@trii/types 2.10.753 → 2.10.754

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (28) hide show
  1. package/dist/Common/Messages/Message.d.ts +2 -0
  2. package/dist/Conversations/Assignment.d.ts +0 -1
  3. package/dist/Conversations/Assignment.js +0 -2
  4. package/dist/Conversations/Classifications/ClassificationTaxonomy.d.ts +0 -2
  5. package/dist/Conversations/Classifications/ClassificationTaxonomy.js +0 -3
  6. package/dist/Conversations/Conversation.d.ts +38 -201
  7. package/dist/Conversations/ConversationPendingTransfer.d.ts +16 -0
  8. package/dist/Conversations/ConversationReadState.d.ts +16 -0
  9. package/dist/Conversations/ConversationReadState.js +2 -0
  10. package/dist/Conversations/ConversationSlaClockState.d.ts +45 -0
  11. package/dist/Conversations/ConversationSlaClockState.js +2 -0
  12. package/dist/Conversations/InboxQueries.d.ts +30 -0
  13. package/dist/Conversations/InboxQueries.js +2 -0
  14. package/dist/Conversations/Outbox.d.ts +0 -1
  15. package/dist/Conversations/Outbox.js +0 -2
  16. package/dist/Conversations/Queue.d.ts +1 -3
  17. package/dist/Conversations/Queue.js +0 -2
  18. package/dist/Conversations/Sla.d.ts +2 -5
  19. package/dist/Conversations/Sla.js +0 -4
  20. package/dist/Conversations/SocialWalls.d.ts +9 -51
  21. package/dist/Conversations/Transfer.d.ts +1 -0
  22. package/dist/Conversations/index.d.ts +4 -2
  23. package/dist/Conversations/index.js +4 -2
  24. package/package.json +1 -1
  25. package/dist/Conversations/InboxMongo.d.ts +0 -154
  26. package/dist/Conversations/InboxMongo.js +0 -128
  27. package/dist/Conversations/InboxProjection.d.ts +0 -162
  28. /package/dist/Conversations/{InboxProjection.js → ConversationPendingTransfer.js} +0 -0
@@ -415,6 +415,8 @@ export interface IMessage {
415
415
  idLocal?: number;
416
416
  spaceId: string;
417
417
  conversationId: string;
418
+ /** Deduplicated incoming ordinal within Conversation; null for outgoing/internal/legacy. */
419
+ conversationIncomingSequence?: number | null;
418
420
  workflowId?: string;
419
421
  /**only for Front-End */
420
422
  contactId?: string;
@@ -1,5 +1,4 @@
1
1
  import { ID } from './Conversation';
2
- export declare const CONVERSATION_ASSIGNMENT_EVENTS_COLLECTION = "conversation_assignment_events";
3
2
  export type ConversationAssignmentEventType = 'enqueued' | 'routing_started' | 'offered' | 'assigned' | 'accepted' | 'transferred' | 'released' | 'timed_out' | 'requeued';
4
3
  export type ConversationAssignmentReason = 'conversation_created' | 'routing_selected' | 'manual_assignment' | 'manual_transfer' | 'flow_routing' | 'offer_timeout' | 'assignment_timeout' | 'agent_accepted' | 'agent_released' | 'supervisor_released' | 'capacity_rebalance' | 'sla_escalation' | 'system_recovery' | 'transfer_cancelled' | 'transfer_declined';
5
4
  export type AssignmentOwnerReference = {
@@ -1,4 +1,2 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.CONVERSATION_ASSIGNMENT_EVENTS_COLLECTION = void 0;
4
- exports.CONVERSATION_ASSIGNMENT_EVENTS_COLLECTION = 'conversation_assignment_events';
@@ -1,6 +1,4 @@
1
1
  import type { ClassificationSeverity } from './ConversationClassificationEvaluation';
2
- export declare const CLASSIFICATION_TAXONOMY_VERSIONS_COLLECTION = "classification_taxonomy_versions";
3
- export declare const CONVERSATION_EVALUATIONS_COLLECTION = "conversation_evaluations";
4
2
  export type ConversationClassificationDimension = 'contact_reason' | 'incident' | 'outcome' | 'recovery';
5
3
  export type ClassificationTaxonomyStatus = 'draft' | 'published' | 'archived';
6
4
  /**
@@ -1,5 +1,2 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.CONVERSATION_EVALUATIONS_COLLECTION = exports.CLASSIFICATION_TAXONOMY_VERSIONS_COLLECTION = void 0;
4
- exports.CLASSIFICATION_TAXONOMY_VERSIONS_COLLECTION = 'classification_taxonomy_versions';
5
- exports.CONVERSATION_EVALUATIONS_COLLECTION = 'conversation_evaluations';
@@ -1,4 +1,7 @@
1
1
  import { UserInfo } from "../Users/UserTrii";
2
+ import type { ConversationReadState } from './ConversationReadState';
3
+ import type { ConversationPendingTransfer } from './ConversationPendingTransfer';
4
+ import type { ConversationSlaSnapshot, ConversationSlaStatus } from './ConversationSlaClockState';
2
5
  import { ILabel } from "./Label";
3
6
  import { GroupInfo } from "./Groups/Group";
4
7
  import { Channels } from "../Common/index";
@@ -38,39 +41,26 @@ export interface IConversation {
38
41
  status: ConversationStatus;
39
42
  priority: 'low' | 'normal' | 'high' | 'urgent';
40
43
  assignment: ConversationAssignmentSnapshot;
41
- /** @deprecated Use owner and assignment.*/
42
- assignedTo?: ConversationAssigned;
43
- sla: {
44
- status: 'none' | 'running' | 'warning' | 'breached' | 'met';
45
- deadlineAt?: Date;
46
- policyId?: string;
47
- version: number;
48
- };
44
+ /** Preserve assignment until acceptance atomically replaces it and clears this handoff. */
45
+ pendingTransfer?: ConversationPendingTransfer | null;
46
+ sla: ConversationSlaSnapshot;
49
47
  lastMessage: string;
50
48
  lastMessageId?: string;
51
- newMessagesCount: number;
52
- newMessagesUnread: boolean;
49
+ readState: ConversationReadState;
53
50
  lastActivityAt: Date;
54
51
  lastCustomerActivityAt?: Date;
55
52
  lastAgentActivityAt?: Date;
56
53
  labels: ILabel[];
57
- /**
58
- * Fijado/favorito. En chats internos (GROUP/DIRECT) el documento está duplicado por miembro
59
- * (`shardKey = "User|{userId}"`), por lo que este flag es por copia/usuario (B1), no del grupo.
60
- */
54
+ /** Shared pin for every viewer of this conversation. */
61
55
  pinned: boolean;
56
+ /** Shared outstanding work; reading does not answer or resolve it. */
57
+ requiresAction: boolean;
62
58
  /**
63
- * Silenciado hasta (B3). Por copia/usuario, igual que `pinned`. null/undefined = no silenciado.
59
+ * Legacy internal-chat preference. Not the shared pin or customer conversation state.
64
60
  * El front compara contra la hora actual; una fecha lejana representa "siempre".
65
61
  */
66
62
  mutedUntil?: Date | null;
67
- /** @deprecated A transfer is represented by an event and the resulting current state. */
68
- transferBy?: string;
69
- /** @deprecated A transfer is represented by an event and the resulting current state. */
70
- transferTo?: ConversationAssigned;
71
63
  expired: boolean;
72
- /** @deprecated Expiration does not own a separate assignment snapshot.*/
73
- expiredAssignedTo: ConversationAssigned;
74
64
  participants: UserInfo[];
75
65
  archived: boolean;
76
66
  spam: boolean;
@@ -93,7 +83,18 @@ export interface IConversation {
93
83
  resolutionReasonId?: string;
94
84
  finalizedAt: Date;
95
85
  finalizedBy?: string;
86
+ /** Presentation kind, not a separate lifecycle. Public conversations belong to one contact. */
87
+ interactionKind: InteractionKind;
88
+ /** Internal Post.id, required for public views. Null for private conversations. */
89
+ wallPostId?: string | null;
90
+ /** Stable internal WallMessage.id root; required for comment/mention, null for wall_post/private.
91
+ * Correlate by tenant/channel/contact/post/root, never by post alone.
92
+ */
93
+ wallRootCommentId?: string | null;
94
+ /** Mutable navigation focus in the public thread, not its identity. */
95
+ lastCommentId?: string | null;
96
96
  }
97
+ export type InteractionKind = 'private' | 'wall_post' | 'comment' | 'mention';
97
98
  export interface IWhatsAppGroupInfo {
98
99
  whatsappGroupId: string;
99
100
  groupInviteCode: string;
@@ -185,7 +186,7 @@ export declare enum ConversationStatus {
185
186
  FINALIZED = 3,// Alias for RESOLVED
186
187
  WAITING = 10
187
188
  }
188
- export type InboxViewId = 'pending' | 'following' | 'sla_warning' | 'sla_breached' | 'team_sla_breached' | 'resolved' | 'label';
189
+ export type InboxViewId = 'pending' | 'following' | 'sla_warning' | 'sla_breached' | 'team_sla_breached';
189
190
  export type OwnerType = 'user' | 'bot' | 'flow' | 'none';
190
191
  export interface NoConversationOwner {
191
192
  type: 'none';
@@ -308,194 +309,41 @@ export interface PutConversationSubscriptionRequest {
308
309
  expectedVersion?: number;
309
310
  notifications: ConversationSubscriptionNotifications;
310
311
  }
311
- export interface UserConversationState {
312
- id: ID;
313
- spaceId: ID;
314
- conversationId: ID;
315
- userId: ID;
316
- pinned: boolean;
317
- pinnedAt?: Date;
318
- lastReadMessageId?: ID;
319
- lastReadAt?: Date;
320
- updatedAt: Date;
321
- version: number;
322
- }
323
312
  export type ConversationReadStateMode = 'read' | 'unread';
324
313
  export interface PutConversationReadStateRequest {
325
314
  mode: ConversationReadStateMode;
326
- lastReadMessageId?: ID;
327
- lastReadAt?: Date;
315
+ commandId: string;
316
+ expectedConversationVersion: number;
317
+ /** Required for read, null for unread. Observed cursor, never silently server-latest. */
318
+ throughIncomingSequence?: number | null;
328
319
  }
329
320
  export interface PutConversationPinRequest {
321
+ commandId: string;
322
+ expectedConversationVersion: number;
330
323
  pinned: boolean;
331
324
  }
332
- export type InboxVisibilityReason = 'manual_follow' | 'assigned_to_user' | 'shared_queue_member' | 'queue_claimable' | 'active_participant' | 'sla_warning_owner' | 'sla_breached_owner' | 'supervised_queue_sla' | 'supervisor_escalation' | 'resolved_visible' | 'labeled';
333
- export interface ConversationInboxSnapshot {
334
- interactionKind: 'private' | 'wall_post' | 'comment' | 'mention';
335
- channelType: string;
336
- channelId?: ID;
337
- contact: {
338
- id?: ID;
339
- displayName: string;
340
- initials?: string;
341
- isBusiness: boolean;
342
- };
343
- status: ConversationStatus;
344
- owner: ConversationOwner;
345
- queueId?: ID;
346
- preview?: string;
347
- lastMessageId?: ID;
348
- unreadCount: number;
349
- priority: 'low' | 'normal' | 'high' | 'urgent';
350
- sla: {
351
- status: 'none' | 'running' | 'warning' | 'breached' | 'met';
352
- deadlineAt?: Date;
353
- };
354
- lastActivityAt: Date;
355
- labels: {
356
- id: ID;
357
- name: string;
358
- color?: string;
359
- }[];
360
- /** Solo poblado para interactionKind='comment'|'mention' - el WallMessage.id del comentario mas
361
- * reciente, para abrir el hilo posicionado ahi (igual que lastMessageId para conversaciones). */
362
- commentId?: ID;
363
- participants: {
364
- id: ID;
365
- displayName?: string;
366
- avatarUrl?: string;
367
- }[];
368
- /**
369
- * Transferencia pendiente que nadie tomo todavia: el assignment vigente
370
- * tiene source='transfer' y status 'offered' o 'queued'.
371
- *
372
- * Resuelta POR USUARIO. `UserInboxItemDocument` es un documento por
373
- * espacio+usuario+conversacion, asi que el backend puede decir de que lado
374
- * queda cada uno — que es lo que la card previa a la migracion del inbox
375
- * calculaba en el cliente comparando el destino contra el uid propio:
376
- *
377
- * - 'in' -> se la transfirieron a este usuario; `counterpartName` es quien
378
- * la transfirio.
379
- * - 'out' -> la transferencia va hacia otro; `counterpartName` es el
380
- * destino (usuario o cola).
381
- *
382
- * Los dos nombres estan a mano en el momento de la transferencia, asi que
383
- * el proyector no necesita releer el historial de eventos.
384
- *
385
- * Ausente cuando no hay transferencia pendiente y en documentos anteriores
386
- * al campo. `counterpartName` no es opcional: sin nombre la fila no puede
387
- * rotular el icono y descarta el indicador, igual que hace con los
388
- * `participants` sin `displayName`.
389
- */
390
- pendingTransfer?: {
391
- direction: 'in' | 'out';
392
- counterpartName: string;
393
- };
394
- }
395
- /**
396
- * Membership of one materialized conversation in one logical inbox view.
397
- *
398
- * visibilityReasons has set semantics: values must be unique and non-empty.
399
- * Removing one reason preserves the view while at least one reason remains.
400
- *
401
- * labelId is only populated when viewId is 'label' - identifies which of the
402
- * space's labels this particular view entry represents (InboxViewId is a
403
- * fixed enum, but labels are dynamic per space).
404
- */
405
- export interface UserInboxItemView {
406
- viewId: InboxViewId;
407
- visibilityReasons: [
408
- InboxVisibilityReason,
409
- ...InboxVisibilityReason[]
410
- ];
411
- requiresAction: boolean;
412
- labelId?: ID;
413
- }
414
- /**
415
- * Materialized read model. There is exactly one document for each
416
- * spaceId + userId + conversationId tuple.
417
- *
418
- * views must contain unique viewId values, EXCEPT for viewId='label': a
419
- * conversation can carry multiple labels, so multiple entries with
420
- * viewId='label' are allowed as long as their labelId differs (unique
421
- * (viewId, labelId) pairs for that case). Delete the document when the last
422
- * view is removed.
423
- */
424
- export interface UserInboxItemDocument {
425
- id: ID;
426
- spaceId: ID;
427
- userId: ID;
428
- conversationId: ID;
429
- views: UserInboxItemView[];
430
- snapshot: ConversationInboxSnapshot;
431
- pinned: boolean;
432
- priorityScore: number;
433
- sortAt: Date;
434
- conversationVersion: number;
435
- subscriptionVersion?: number;
436
- projectionVersion: number;
437
- lastSourceEventId: ID;
438
- projectedAt: Date;
439
- }
440
- /**
441
- * Materialized counters for exactly one user/view pair (user/view/label when
442
- * viewId is 'label' - labelId becomes part of the logical key in that case).
443
- *
444
- * total and requiresAction count conversations, never visibility reasons.
445
- * unread counts conversations whose snapshot.unreadCount is greater than zero,
446
- * not the number of unread messages.
447
- */
448
- export interface UserInboxCounterDocument {
449
- id: ID;
450
- spaceId: ID;
451
- userId: ID;
452
- viewId: InboxViewId;
453
- labelId?: ID;
454
- total: number;
455
- requiresAction: number;
456
- unread: number;
457
- projectionVersion: number;
458
- updatedAt: Date;
459
- }
325
+ /** Conversation lists return CursorPage<IConversation> directly; authorization stays in backend. */
460
326
  export interface CursorPage<T> {
461
327
  items: T[];
462
- nextCursor?: string;
328
+ nextCursor?: string | null;
463
329
  hasMore: boolean;
464
330
  generatedAt: Date;
465
331
  }
466
- export interface ConversationCapabilitiesDto {
467
- canView: boolean;
468
- canReply: boolean;
469
- canFollow: boolean;
470
- canAbandon: boolean;
471
- canClaim: boolean;
472
- canAccept: boolean;
473
- canRelease: boolean;
474
- canTransfer: boolean;
475
- canResolve: boolean;
476
- canReopen: boolean;
477
- canChangePriority: boolean;
478
- canManageLabels: boolean;
479
- canManageSla: boolean;
480
- canViewAssignmentHistory: boolean;
481
- canViewRoutingDecisions: boolean;
482
- }
332
+ /** Counts distinct authorized conversations on demand; unread includes manual unread. */
483
333
  export interface InboxViewSummaryDto {
484
- id: InboxViewId | 'supervised_target' | `label:${string}`;
334
+ id: string;
485
335
  label: string;
486
- scope: 'personal' | 'supervision' | 'history' | 'labels';
336
+ scope: string;
487
337
  order: number;
488
338
  total: number;
489
339
  requiresAction: number;
490
340
  unread: number;
491
341
  enabled: boolean;
492
342
  targetRequired: boolean;
493
- capabilities: string[];
494
343
  }
495
344
  export interface InboxViewsResponse {
496
345
  views: InboxViewSummaryDto[];
497
346
  countersGeneratedAt: Date;
498
- projectionLagMs?: number;
499
347
  }
500
348
  export interface InboxFilterDto {
501
349
  text?: string;
@@ -504,7 +352,8 @@ export interface InboxFilterDto {
504
352
  channelTypes?: string[];
505
353
  statuses?: ConversationStatus[];
506
354
  priorities?: Array<'low' | 'normal' | 'high' | 'urgent'>;
507
- slaStatuses?: Array<'none' | 'running' | 'warning' | 'breached' | 'met'>;
355
+ slaStatuses?: ConversationSlaStatus[];
356
+ /** OR within the authorized view; labels never grant access. */
508
357
  labelIds?: ID[];
509
358
  unread?: boolean;
510
359
  requiresAction?: boolean;
@@ -519,16 +368,6 @@ export interface InboxFilterDto {
519
368
  type: 'priority';
520
369
  };
521
370
  }
522
- export type InboxConversationItemDto = Pick<UserInboxItemDocument, 'conversationId' | 'views' | 'snapshot' | 'pinned' | 'priorityScore' | 'sortAt'> & {
523
- capabilities: Pick<ConversationCapabilitiesDto, 'canView' | 'canReply' | 'canClaim' | 'canTransfer'>;
524
- };
525
- export interface InboxConversationPage extends CursorPage<InboxConversationItemDto> {
526
- appliedFilter: InboxFilterDto;
527
- facets?: unknown;
528
- }
529
- /**
530
- * DTO API - GET /inbox/views/{viewId}/filter-options.
531
- */
532
371
  export interface InboxFacetOptionDto {
533
372
  id: string;
534
373
  label: string;
@@ -541,9 +380,11 @@ export interface InboxFacetsDto {
541
380
  statuses: InboxFacetOptionDto[];
542
381
  priorities: InboxFacetOptionDto[];
543
382
  slaStatuses: InboxFacetOptionDto[];
383
+ /** Full selected view, before label filters/pagination; only positive counts, once per label/conversation. */
544
384
  labels: InboxFacetOptionDto[];
545
385
  }
546
386
  export interface InboxFilterOptionsResponse {
387
+ viewId: InboxViewId;
547
388
  facets: InboxFacetsDto;
548
389
  generatedAt: Date;
549
390
  }
@@ -558,14 +399,10 @@ export interface ConversationUserRelationDto {
558
399
  isOwner: boolean;
559
400
  isParticipant: boolean;
560
401
  isFollowing: boolean;
561
- isPinned: boolean;
562
- unreadCount: number;
563
402
  }
564
403
  export interface ConversationDetailResponse {
565
404
  conversation: IConversation;
566
405
  relation: ConversationUserRelationDto;
567
- capabilities: ConversationCapabilitiesDto;
568
- availableActions: string[];
569
406
  }
570
407
  export interface AddConversationParticipantRequest {
571
408
  userId: string;
@@ -0,0 +1,16 @@
1
+ import type { TransferConversationReason, TransferConversationTarget } from './Transfer';
2
+ /** Single current handoff, not history. Keep assignment unchanged until acceptance.
3
+ * Accept atomically with matching transferId, sourceAssignmentId and Conversation.version.
4
+ * First valid acceptance wins. Reject/cancel/expire clears this state without moving source.
5
+ * Resolve authorized target audience at query time, checking expiration even before cleanup.
6
+ */
7
+ export interface ConversationPendingTransfer {
8
+ transferId: string;
9
+ sourceAssignmentId: string;
10
+ target: TransferConversationTarget;
11
+ /** Null for automated requests; full actor is recorded in events. */
12
+ requestedByUserId?: string | null;
13
+ requestedAt: Date;
14
+ expiresAt?: Date | null;
15
+ reason: TransferConversationReason;
16
+ }
@@ -0,0 +1,16 @@
1
+ /** Shared state; 0 <= readThroughSequence <= lastIncomingSequence.
2
+ * Incoming ordinals are assigned once per deduplicated message in ingestion order.
3
+ * Reading advances only to the observed ordinal; manual unread preserves the cursor.
4
+ * Numeric counters must remain within JavaScript's safe integer range.
5
+ */
6
+ export interface ConversationReadState {
7
+ lastIncomingSequence: number;
8
+ readThroughSequence: number;
9
+ manuallyMarkedUnread: boolean;
10
+ changedAt?: Date | null;
11
+ changedByUserId?: string | null;
12
+ /** Derived by the backend; not persisted in BSON. */
13
+ readonly unreadCount: number;
14
+ /** Includes manual unread even when unreadCount is zero. */
15
+ readonly isUnread: boolean;
16
+ }
@@ -0,0 +1,2 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
@@ -0,0 +1,45 @@
1
+ import type { ConversationSlaMetric, ConversationSlaCycleStatus } from './Sla';
2
+ export type ConversationSlaStatus = 'none' | 'running' | 'warning' | 'breached' | 'met' | 'paused' | 'cancelled';
3
+ /** Current operational state, at most one clock per metric. No configuration/history.
4
+ * A replacement gets a new cycleId; archive the previous cycle outside Conversation.
5
+ * Updates use Conversation.version plus expected cycleId/SLA revision. All times UTC.
6
+ */
7
+ export interface ConversationSlaClockState {
8
+ cycleId: string;
9
+ metric: ConversationSlaMetric;
10
+ sequence: number;
11
+ /** breached is unfinished; late completion preserves breachedAt. */
12
+ status: ConversationSlaCycleStatus;
13
+ /** Immutable applied reference must resolve historical calendar/timezone rules. */
14
+ policyId: string;
15
+ policyVersion: number;
16
+ startedByEventId: string;
17
+ startedByMessageId?: string | null;
18
+ assignmentId?: string | null;
19
+ startedAt: Date;
20
+ deadlineAt?: Date | null;
21
+ nextWarningAt?: Date | null;
22
+ warningAt?: Date | null;
23
+ /** Current pause only; clear on resume. */
24
+ pausedAt?: Date | null;
25
+ /** Chargeable seconds through elapsedCalculatedAt, within JS safe integer range. */
26
+ elapsedSeconds: number;
27
+ /** Accounting watermark; on resume reset to resume time preserving consumed seconds. */
28
+ elapsedCalculatedAt: Date;
29
+ breachedAt?: Date | null;
30
+ completedAt?: Date | null;
31
+ cancelledAt?: Date | null;
32
+ }
33
+ /** Summary updated atomically with clocks; never an independent state source. */
34
+ export interface ConversationSlaSnapshot {
35
+ /** Precedence: breached, active warning, running, paused, met, cancelled, none. */
36
+ status: ConversationSlaStatus;
37
+ /** Earliest unfinished/unpaused deadline. Compare against UTC even if evaluator lags. */
38
+ deadlineAt?: Date | null;
39
+ /** Selection for new clocks; current clocks retain their applied references. */
40
+ policyId?: string | null;
41
+ policyVersion?: number | null;
42
+ clocks: ConversationSlaClockState[];
43
+ /** SLA state revision, not policy version. */
44
+ version: number;
45
+ }
@@ -0,0 +1,2 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
@@ -0,0 +1,30 @@
1
+ import type { ID, InboxViewId, InboxFilterDto } from './Conversation';
2
+ export type InboxPaginationSort = 'activity' | 'priority';
3
+ /** Cursor over current authorized conversations, after filtering.
4
+ * Bind to tenant/user/view/filter/sort; live updates may move rows between pages.
5
+ */
6
+ export interface InboxActivityCursor {
7
+ sort: 'activity';
8
+ pinned: boolean;
9
+ lastActivityAt: Date;
10
+ conversationId: ID;
11
+ }
12
+ export interface InboxPriorityCursor {
13
+ sort: 'priority';
14
+ pinned: boolean;
15
+ /** Explicit rank urgent > high > normal > low, not lexical order. */
16
+ priority: 'low' | 'normal' | 'high' | 'urgent';
17
+ lastActivityAt: Date;
18
+ conversationId: ID;
19
+ }
20
+ export type InboxPaginationCursor = InboxActivityCursor | InboxPriorityCursor;
21
+ export interface InboxPageQuery {
22
+ /** Trusted server context, not client authorization claims. */
23
+ spaceId: ID;
24
+ userId: ID;
25
+ viewId: InboxViewId;
26
+ limit: number;
27
+ filter: InboxFilterDto;
28
+ /** Must match filter.sort. */
29
+ cursor?: InboxPaginationCursor | null;
30
+ }
@@ -0,0 +1,2 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
@@ -1,4 +1,3 @@
1
- export declare const OUTBOX_EVENTS_COLLECTION = "outbox_events";
2
1
  export type OutboxEventStatus = 'pending' | 'publishing' | 'published' | 'failed';
3
2
  /**
4
3
  * Transactional outbox record persisted with the aggregate mutation.
@@ -1,4 +1,2 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.OUTBOX_EVENTS_COLLECTION = void 0;
4
- exports.OUTBOX_EVENTS_COLLECTION = 'outbox_events';
@@ -1,6 +1,5 @@
1
1
  import { ID } from './Conversation';
2
2
  import { QueueRoutingPolicy } from './Routing';
3
- export declare const CONVERSATION_QUEUES_COLLECTION = "conversation_queues";
4
3
  export type QueueSlaTransferBehavior = 'preserve' | 'restart' | 'recalculate';
5
4
  export type ConversationQueueSlaConfig = {
6
5
  enabled: false;
@@ -21,15 +20,14 @@ export type ConversationQueueSlaConfig = {
21
20
  */
22
21
  export type QueueMemberAudienceMode = 'distribution' | 'all_current' | 'none';
23
22
  export type QueueSupervisorAudienceMode = 'all_current' | 'sla_only' | 'escalations_only' | 'none';
23
+ /** Open conversations only. Finalization retains authorized following, not queue visibility. */
24
24
  export interface ConversationQueueAudienceConfig {
25
25
  members: {
26
26
  mode: QueueMemberAudienceMode;
27
27
  requireActiveParticipation: boolean;
28
- includeResolved: boolean;
29
28
  };
30
29
  supervisors: {
31
30
  mode: QueueSupervisorAudienceMode;
32
- includeResolved: boolean;
33
31
  };
34
32
  }
35
33
  export interface ConversationQueueInfo {
@@ -1,4 +1,2 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.CONVERSATION_QUEUES_COLLECTION = void 0;
4
- exports.CONVERSATION_QUEUES_COLLECTION = 'conversation_queues';
@@ -1,9 +1,6 @@
1
1
  import type { ISchedule } from '../Common/Schedules/Schedules';
2
2
  import type { ConversationEventType, ConversationStatus, ID } from './Conversation';
3
3
  import type { OutboxEvent } from './Outbox';
4
- export declare const CONVERSATION_SLA_POLICIES_COLLECTION = "conversation_sla_policies";
5
- export declare const CONVERSATION_SLA_CYCLES_COLLECTION = "conversation_sla_cycles";
6
- export declare const CONVERSATION_SLA_EVENTS_COLLECTION = "conversation_sla_events";
7
4
  export type ConversationSlaMetric = 'first_response' | 'next_response' | 'assignment' | 'acceptance' | 'resolution';
8
5
  /**
9
6
  * continuous counts natural elapsed time, including nights and holidays.
@@ -166,8 +163,8 @@ export interface ConversationSlaPausePeriod {
166
163
  sourceEventId: ID;
167
164
  }
168
165
  /**
169
- * Durable metric cycle. The target and time calculation are copied from the
170
- * selected policy version so later policy edits cannot alter active history.
166
+ * External history/read model. Current clocks live only in Conversation.sla.clocks.
167
+ * Policy copies here describe historical configuration, not another live state source.
171
168
  */
172
169
  export interface ConversationSlaCycle {
173
170
  id: ID;
@@ -1,6 +1,2 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.CONVERSATION_SLA_EVENTS_COLLECTION = exports.CONVERSATION_SLA_CYCLES_COLLECTION = exports.CONVERSATION_SLA_POLICIES_COLLECTION = void 0;
4
- exports.CONVERSATION_SLA_POLICIES_COLLECTION = 'conversation_sla_policies';
5
- exports.CONVERSATION_SLA_CYCLES_COLLECTION = 'conversation_sla_cycles';
6
- exports.CONVERSATION_SLA_EVENTS_COLLECTION = 'conversation_sla_events';
@@ -1,7 +1,6 @@
1
1
  import { Channels } from "../Common/index";
2
2
  import { UserInfo } from "../Users/UserTrii";
3
- import { ConversationAssignmentSnapshot, ConversationCapabilitiesDto, ConversationStatus, ConversationUserRelationDto } from "./Conversation";
4
- import { ILabel } from "./Label";
3
+ import { ConversationAssignmentSnapshot, IConversation, InteractionKind, ConversationUserRelationDto } from "./Conversation";
5
4
  export interface Post {
6
5
  id: string;
7
6
  spaceId: string;
@@ -30,6 +29,9 @@ export interface PostStats {
30
29
  }
31
30
  export interface WallMessage {
32
31
  id: string;
32
+ /** Canonical conversation owning shared read/SLA state. */
33
+ conversationId?: string | null;
34
+ conversationIncomingSequence?: number | null;
33
35
  spaceId: string;
34
36
  postId: string;
35
37
  parentId: string;
@@ -75,56 +77,12 @@ export interface ExternalUserInfo {
75
77
  name: string;
76
78
  imageUrl: string;
77
79
  }
78
- /**
79
- * Estado de una interaccion wall (post/comment/mention), equivalente a lo que para una
80
- * conversacion vive directo en IConversation. Post no tiene estos campos persistidos (no hay
81
- * assignment/sla reales para posts todavia), asi que este objeto se arma al vuelo del lado del
82
- * backend a partir de UserInboxItemDocument.snapshot (owner/status/queueId/unreadCount) y del
83
- * propio Post (participants/channelInfo/fechas).
84
- */
85
- export interface WallInteractionState {
86
- id: string;
87
- spaceId: string;
88
- status: ConversationStatus;
89
- priority: 'low' | 'normal' | 'high' | 'urgent';
90
- assignment: ConversationAssignmentSnapshot;
91
- sla: {
92
- status: 'none' | 'running' | 'warning' | 'breached' | 'met';
93
- deadlineAt?: Date;
94
- policyId?: string;
95
- version: number;
96
- };
97
- labels: ILabel[];
98
- participants: UserInfo[];
99
- channelInfo: Channels.IChannelInfo;
100
- unreadCount: number;
101
- lastActivityAt: Date;
102
- createdAt: Date;
103
- updatedAt: Date;
104
- finalizedAt?: Date;
105
- }
106
- /**
107
- * DTO API - GET /Walls/{postId}/detail. Rama "wall" del sobre que en conversaciones da
108
- * ConversationDetailResponse. interactionKind viaja igual (wall_post/comment/mention) aunque el
109
- * front ya sepa que pego en el endpoint de walls, por si quiere distinguir el motivo de la fila.
110
- */
80
+ /** Public content plus its canonical conversation; no independent attention lifecycle. */
111
81
  export interface WallPostDetailResponse {
112
- interactionKind: 'wall_post' | 'comment' | 'mention';
82
+ interactionKind: InteractionKind;
113
83
  post: Post;
114
- interaction: WallInteractionState;
115
- /** Comentario que origino la interaccion (comment/mention). Ausente para wall_post o cuando
116
- * todavia no se implementa el tracking de comentario puntual (P1, ver discusion). */
117
- focusCommentId?: string;
84
+ conversation: IConversation;
85
+ /** Navigation focus only; stable identity is conversation.wallRootCommentId. */
86
+ focusCommentId?: string | null;
118
87
  relation: ConversationUserRelationDto;
119
- capabilities: ConversationCapabilitiesDto;
120
- availableActions: string[];
121
- }
122
- /**
123
- * DTO API - GET /inbox/items/{id}. Resuelve deep-link (F5, link compartido) sin tener el snapshot
124
- * del listado: solo dice de que tipo es el id para que el front pegue en
125
- * /Conversations/{id}/detail o en /Walls/{postId}/detail segun corresponda.
126
- */
127
- export interface InboxItemKindDto {
128
- id: string;
129
- interactionKind: 'private' | 'wall_post' | 'comment' | 'mention';
130
88
  }
@@ -5,6 +5,7 @@ export type TransferConversationReason = Extract<ConversationAssignmentReason, '
5
5
  export type TransferConversationTarget = {
6
6
  type: 'queue';
7
7
  queueId: ID;
8
+ userId?: null;
8
9
  } | {
9
10
  type: 'user';
10
11
  queueId: ID;
@@ -2,8 +2,10 @@ export * as Flows from './Flows/index';
2
2
  export * as Groups from './Groups/Group';
3
3
  export * from './Assignment';
4
4
  export * from './Conversation';
5
- export * from './InboxMongo';
6
- export * from './InboxProjection';
5
+ export * from './InboxQueries';
6
+ export * from './ConversationReadState';
7
+ export * from './ConversationPendingTransfer';
8
+ export * from './ConversationSlaClockState';
7
9
  export * from './Outbox';
8
10
  export * from './Ending';
9
11
  export * from './Label';
@@ -41,8 +41,10 @@ exports.Flows = __importStar(require("./Flows/index"));
41
41
  exports.Groups = __importStar(require("./Groups/Group"));
42
42
  __exportStar(require("./Assignment"), exports);
43
43
  __exportStar(require("./Conversation"), exports);
44
- __exportStar(require("./InboxMongo"), exports);
45
- __exportStar(require("./InboxProjection"), exports);
44
+ __exportStar(require("./InboxQueries"), exports);
45
+ __exportStar(require("./ConversationReadState"), exports);
46
+ __exportStar(require("./ConversationPendingTransfer"), exports);
47
+ __exportStar(require("./ConversationSlaClockState"), exports);
46
48
  __exportStar(require("./Outbox"), exports);
47
49
  __exportStar(require("./Ending"), exports);
48
50
  __exportStar(require("./Label"), exports);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@trii/types",
3
- "version": "2.10.753",
3
+ "version": "2.10.754",
4
4
  "description": "Types definitions for Trii projects - ",
5
5
  "main": "dist/index.js",
6
6
  "types": "dist/index.d.ts",
@@ -1,154 +0,0 @@
1
- import { ID, InboxViewId } from './Conversation';
2
- export declare const CONVERSATION_SUBSCRIPTIONS_COLLECTION = "conversation_subscriptions";
3
- export declare const USER_INBOX_ITEMS_COLLECTION = "user_inbox_items";
4
- export declare const USER_INBOX_COUNTERS_COLLECTION = "user_inbox_counters";
5
- export declare const INBOX_PROCESSOR_RECEIPTS_COLLECTION = "inbox_processor_receipts";
6
- /**
7
- * MongoDB indexes for conversation_subscriptions.
8
- *
9
- * The unique index enforces one current subscription state per
10
- * user/conversation. The follower index resolves the active followers that
11
- * InboxAudienceResolver receives as input.
12
- */
13
- export declare const CONVERSATION_SUBSCRIPTION_INDEXES: readonly [{
14
- readonly name: "uq_subscription_user_conversation";
15
- readonly key: {
16
- readonly spaceId: 1;
17
- readonly userId: 1;
18
- readonly conversationId: 1;
19
- };
20
- readonly unique: true;
21
- }, {
22
- readonly name: "ix_subscription_conversation_followers";
23
- readonly key: {
24
- readonly spaceId: 1;
25
- readonly conversationId: 1;
26
- readonly status: 1;
27
- readonly userId: 1;
28
- };
29
- }];
30
- /**
31
- * MongoDB indexes for user_inbox_items.
32
- *
33
- * views.viewId is the only array field in each compound index. Do not add
34
- * views.visibilityReasons to these indexes: MongoDB compound multikey indexes
35
- * must not combine multiple array paths.
36
- */
37
- export declare const USER_INBOX_ITEM_INDEXES: readonly [{
38
- readonly name: "uq_inbox_item_user_conversation";
39
- readonly key: {
40
- readonly spaceId: 1;
41
- readonly userId: 1;
42
- readonly conversationId: 1;
43
- };
44
- readonly unique: true;
45
- }, {
46
- readonly name: "ix_inbox_view_activity";
47
- readonly key: {
48
- readonly spaceId: 1;
49
- readonly userId: 1;
50
- readonly 'views.viewId': 1;
51
- readonly pinned: -1;
52
- readonly sortAt: -1;
53
- readonly id: -1;
54
- };
55
- }, {
56
- readonly name: "ix_inbox_view_priority";
57
- readonly key: {
58
- readonly spaceId: 1;
59
- readonly userId: 1;
60
- readonly 'views.viewId': 1;
61
- readonly pinned: -1;
62
- readonly priorityScore: -1;
63
- readonly sortAt: -1;
64
- readonly id: -1;
65
- };
66
- }, {
67
- readonly name: "ix_inbox_view_status_activity";
68
- readonly key: {
69
- readonly spaceId: 1;
70
- readonly userId: 1;
71
- readonly 'views.viewId': 1;
72
- readonly 'snapshot.status': 1;
73
- readonly sortAt: -1;
74
- readonly id: -1;
75
- };
76
- }, {
77
- readonly name: "ix_inbox_view_queue_activity";
78
- readonly key: {
79
- readonly spaceId: 1;
80
- readonly userId: 1;
81
- readonly 'views.viewId': 1;
82
- readonly 'snapshot.queueId': 1;
83
- readonly sortAt: -1;
84
- readonly id: -1;
85
- };
86
- }];
87
- /**
88
- * MongoDB indexes for user_inbox_counters.
89
- *
90
- * The unique index also serves reads for all views of a user through its
91
- * spaceId + userId prefix.
92
- */
93
- export declare const USER_INBOX_COUNTER_INDEXES: readonly [{
94
- readonly name: "uq_inbox_counter_user_view";
95
- readonly key: {
96
- readonly spaceId: 1;
97
- readonly userId: 1;
98
- readonly viewId: 1;
99
- };
100
- readonly unique: true;
101
- }];
102
- /**
103
- * MongoDB indexes for durable processor idempotency receipts.
104
- */
105
- export declare const INBOX_PROCESSOR_RECEIPT_INDEXES: readonly [{
106
- readonly name: "uq_inbox_processor_source_scope";
107
- readonly key: {
108
- readonly processor: 1;
109
- readonly sourceEventId: 1;
110
- readonly scopeKey: 1;
111
- };
112
- readonly unique: true;
113
- }];
114
- export type InboxPaginationSort = 'activity' | 'priority';
115
- /**
116
- * Seek-pagination cursor matching ix_inbox_view_activity.
117
- */
118
- export interface InboxActivityCursor {
119
- sort: 'activity';
120
- pinned: boolean;
121
- sortAt: Date;
122
- id: ID;
123
- }
124
- /**
125
- * Seek-pagination cursor matching ix_inbox_view_priority.
126
- */
127
- export interface InboxPriorityCursor {
128
- sort: 'priority';
129
- pinned: boolean;
130
- priorityScore: number;
131
- sortAt: Date;
132
- id: ID;
133
- }
134
- export type InboxPaginationCursor = InboxActivityCursor | InboxPriorityCursor;
135
- /**
136
- * Query contract for one logical inbox view.
137
- *
138
- * Implementations must use seek pagination with cursor; skip pagination is not
139
- * compatible with frequently changing sortAt values.
140
- */
141
- interface InboxPageQueryBase {
142
- spaceId: ID;
143
- userId: ID;
144
- viewId: InboxViewId;
145
- limit: number;
146
- }
147
- export type InboxPageQuery = InboxPageQueryBase & {
148
- sort: 'activity';
149
- cursor?: InboxActivityCursor;
150
- } | InboxPageQueryBase & {
151
- sort: 'priority';
152
- cursor?: InboxPriorityCursor;
153
- };
154
- export {};
@@ -1,128 +0,0 @@
1
- "use strict";
2
- Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.INBOX_PROCESSOR_RECEIPT_INDEXES = exports.USER_INBOX_COUNTER_INDEXES = exports.USER_INBOX_ITEM_INDEXES = exports.CONVERSATION_SUBSCRIPTION_INDEXES = exports.INBOX_PROCESSOR_RECEIPTS_COLLECTION = exports.USER_INBOX_COUNTERS_COLLECTION = exports.USER_INBOX_ITEMS_COLLECTION = exports.CONVERSATION_SUBSCRIPTIONS_COLLECTION = void 0;
4
- exports.CONVERSATION_SUBSCRIPTIONS_COLLECTION = 'conversation_subscriptions';
5
- exports.USER_INBOX_ITEMS_COLLECTION = 'user_inbox_items';
6
- exports.USER_INBOX_COUNTERS_COLLECTION = 'user_inbox_counters';
7
- exports.INBOX_PROCESSOR_RECEIPTS_COLLECTION = 'inbox_processor_receipts';
8
- /**
9
- * MongoDB indexes for conversation_subscriptions.
10
- *
11
- * The unique index enforces one current subscription state per
12
- * user/conversation. The follower index resolves the active followers that
13
- * InboxAudienceResolver receives as input.
14
- */
15
- exports.CONVERSATION_SUBSCRIPTION_INDEXES = [
16
- {
17
- name: 'uq_subscription_user_conversation',
18
- key: {
19
- spaceId: 1,
20
- userId: 1,
21
- conversationId: 1,
22
- },
23
- unique: true,
24
- },
25
- {
26
- name: 'ix_subscription_conversation_followers',
27
- key: {
28
- spaceId: 1,
29
- conversationId: 1,
30
- status: 1,
31
- userId: 1,
32
- },
33
- },
34
- ];
35
- /**
36
- * MongoDB indexes for user_inbox_items.
37
- *
38
- * views.viewId is the only array field in each compound index. Do not add
39
- * views.visibilityReasons to these indexes: MongoDB compound multikey indexes
40
- * must not combine multiple array paths.
41
- */
42
- exports.USER_INBOX_ITEM_INDEXES = [
43
- {
44
- name: 'uq_inbox_item_user_conversation',
45
- key: {
46
- spaceId: 1,
47
- userId: 1,
48
- conversationId: 1,
49
- },
50
- unique: true,
51
- },
52
- {
53
- name: 'ix_inbox_view_activity',
54
- key: {
55
- spaceId: 1,
56
- userId: 1,
57
- 'views.viewId': 1,
58
- pinned: -1,
59
- sortAt: -1,
60
- id: -1,
61
- },
62
- },
63
- {
64
- name: 'ix_inbox_view_priority',
65
- key: {
66
- spaceId: 1,
67
- userId: 1,
68
- 'views.viewId': 1,
69
- pinned: -1,
70
- priorityScore: -1,
71
- sortAt: -1,
72
- id: -1,
73
- },
74
- },
75
- {
76
- name: 'ix_inbox_view_status_activity',
77
- key: {
78
- spaceId: 1,
79
- userId: 1,
80
- 'views.viewId': 1,
81
- 'snapshot.status': 1,
82
- sortAt: -1,
83
- id: -1,
84
- },
85
- },
86
- {
87
- name: 'ix_inbox_view_queue_activity',
88
- key: {
89
- spaceId: 1,
90
- userId: 1,
91
- 'views.viewId': 1,
92
- 'snapshot.queueId': 1,
93
- sortAt: -1,
94
- id: -1,
95
- },
96
- },
97
- ];
98
- /**
99
- * MongoDB indexes for user_inbox_counters.
100
- *
101
- * The unique index also serves reads for all views of a user through its
102
- * spaceId + userId prefix.
103
- */
104
- exports.USER_INBOX_COUNTER_INDEXES = [
105
- {
106
- name: 'uq_inbox_counter_user_view',
107
- key: {
108
- spaceId: 1,
109
- userId: 1,
110
- viewId: 1,
111
- },
112
- unique: true,
113
- },
114
- ];
115
- /**
116
- * MongoDB indexes for durable processor idempotency receipts.
117
- */
118
- exports.INBOX_PROCESSOR_RECEIPT_INDEXES = [
119
- {
120
- name: 'uq_inbox_processor_source_scope',
121
- key: {
122
- processor: 1,
123
- sourceEventId: 1,
124
- scopeKey: 1,
125
- },
126
- unique: true,
127
- },
128
- ];
@@ -1,162 +0,0 @@
1
- import { ConversationInboxSnapshot, ID, InboxViewId, UserInboxItemView } from './Conversation';
2
- import { ConversationQueue } from './Queue';
3
- import { ConversationAssignmentChanged } from './Transfer';
4
- export type InboxProcessorName = 'inbox-audience-resolver' | 'inbox-item-projector' | 'inbox-counter-projector';
5
- /**
6
- * Queue membership snapshot used while resolving an assignment change.
7
- *
8
- * Produced by: assignment-change consumer.
9
- * Consumed by: InboxAudienceResolver.
10
- * Used when: previous/current queue audiences must be expanded from groups
11
- * into concrete users without embedding membership in the integration event.
12
- */
13
- export interface QueueAudienceResolutionContext {
14
- queue: ConversationQueue;
15
- memberUserIds: ID[];
16
- activeMemberUserIds: ID[];
17
- supervisorUserIds: ID[];
18
- }
19
- /**
20
- * Complete input for calculating desired inbox visibility.
21
- *
22
- * Produced by: ConversationAssignmentChanged consumer.
23
- * Consumed by: InboxAudienceResolver.
24
- * Used when: an assignment/transfer changes queue, owner or status and the
25
- * desired per-user inbox audience must be recalculated.
26
- */
27
- export interface InboxAudienceResolverInput {
28
- sourceEvent: ConversationAssignmentChanged;
29
- snapshot: ConversationInboxSnapshot;
30
- previousQueue?: QueueAudienceResolutionContext;
31
- currentQueue?: QueueAudienceResolutionContext;
32
- participantUserIds: ID[];
33
- followerUserIds: ID[];
34
- }
35
- /**
36
- * Visibility of one conversation inside one logical inbox view.
37
- *
38
- * Produced by: InboxAudienceResolver.
39
- * Consumed by: inbox item projection planner/projector.
40
- * Used when: multiple independent reasons can preserve the same view without
41
- * generating duplicate inbox items or duplicate counter increments.
42
- */
43
- export type ResolvedInboxView = UserInboxItemView;
44
- /**
45
- * Desired inbox visibility for a single user after applying the source event.
46
- *
47
- * Produced by: InboxAudienceResolver.
48
- * Consumed by: inbox item projection planner/projector.
49
- * Used when: the projector compares desired visibility with the materialized
50
- * user_inbox_items state.
51
- */
52
- export interface ResolvedInboxUserVisibility {
53
- userId: ID;
54
- views: ResolvedInboxView[];
55
- }
56
- /**
57
- * Full audience resolution result for one conversation version.
58
- *
59
- * Produced by: InboxAudienceResolver.
60
- * Consumed by: inbox item projection planner/projector.
61
- * Used when: all affected users must be diffed against their existing
62
- * user_inbox_items documents.
63
- */
64
- export interface ResolvedInboxAudience {
65
- sourceEventId: ID;
66
- spaceId: ID;
67
- conversationId: ID;
68
- conversationVersion: number;
69
- snapshot: ConversationInboxSnapshot;
70
- users: ResolvedInboxUserVisibility[];
71
- resolvedAt: Date;
72
- }
73
- /**
74
- * Processor boundary for pure audience calculation.
75
- *
76
- * Called by: ConversationAssignmentChanged consumer.
77
- * Returns to: inbox item projection planner/projector.
78
- */
79
- export interface InboxAudienceResolver {
80
- resolve(input: InboxAudienceResolverInput): Promise<ResolvedInboxAudience>;
81
- }
82
- export type InboxProjectionOperation = 'insert' | 'update' | 'delete';
83
- /**
84
- * Idempotent mutation plan for one user's materialized conversation item.
85
- *
86
- * Produced by: inbox item projection planner.
87
- * Consumed by: inbox item projector; after persistence it is also the input
88
- * used to derive InboxCounterDelta messages.
89
- * Used when: desired audience is compared with the existing projection.
90
- *
91
- * id must be deterministic from:
92
- * sourceEventId + userId + conversationId.
93
- */
94
- export interface InboxProjectionDelta {
95
- id: ID;
96
- sourceEventId: ID;
97
- spaceId: ID;
98
- userId: ID;
99
- conversationId: ID;
100
- conversationVersion: number;
101
- operation: InboxProjectionOperation;
102
- previous?: ResolvedInboxUserVisibility;
103
- current?: ResolvedInboxUserVisibility;
104
- snapshot?: ConversationInboxSnapshot;
105
- createdAt: Date;
106
- }
107
- /**
108
- * Atomic counter change for one user/view caused by one projection delta.
109
- *
110
- * Produced by: inbox item projector after determining the actual persisted
111
- * before/after state.
112
- * Consumed by: InboxCounterProjector.
113
- * Used when: view membership, requiresAction or unread state changes.
114
- *
115
- * id must be deterministic from:
116
- * sourceEventId + userId + conversationId + viewId.
117
- */
118
- export interface InboxCounterDelta {
119
- id: ID;
120
- sourceEventId: ID;
121
- spaceId: ID;
122
- userId: ID;
123
- conversationId: ID;
124
- viewId: InboxViewId;
125
- conversationVersion: number;
126
- totalDelta: -1 | 0 | 1;
127
- requiresActionDelta: -1 | 0 | 1;
128
- unreadDelta: -1 | 0 | 1;
129
- createdAt: Date;
130
- }
131
- /**
132
- * Durable idempotency receipt for a processor side effect.
133
- *
134
- * Produced and consumed by: inbox item projector or InboxCounterProjector.
135
- * Used when: the same outbox event/delta is delivered more than once.
136
- *
137
- * Unique key:
138
- * processor + sourceEventId + scopeKey.
139
- *
140
- * scopeKey examples:
141
- * - item projector: userId + conversationId
142
- * - counter projector: userId + conversationId + viewId
143
- */
144
- export interface InboxProcessorReceipt {
145
- id: ID;
146
- processor: InboxProcessorName;
147
- sourceEventId: ID;
148
- scopeKey: string;
149
- spaceId: ID;
150
- conversationId: ID;
151
- conversationVersion: number;
152
- processedAt: Date;
153
- }
154
- /**
155
- * Processor boundary for applying counter deltas idempotently.
156
- *
157
- * Called by: inbox item projector.
158
- * Mutates: user_inbox_counters and InboxProcessorReceipt in one transaction.
159
- */
160
- export interface InboxCounterProjector {
161
- apply(delta: InboxCounterDelta): Promise<void>;
162
- }