/** * @factory/shared/realtime/comment-events — the comment stream-event → * `comment.event` frame-kind mapping (TASK-0094 R1). * * Two layers, one emission (recorded in the task file): * - the PUBLIC stream events on `factory:events` (the Phase-1 catalog: * `comment.created` / `comment.resolved` — Phase-13 webhooks listen here); * - the ROOM-INTERNAL `comment.event` frame, whose `kind` set is * `created | replied | resolved | reopened | reacted | deleted` (the * comment panel's full reactivity). * * The API's CommentModule emits ONE event per operation; this mapping * derives the frame kind from the event name + payload: * - `comment.created` → `replied` when the payload carries a * `replyId` (a reply is a comment with a parent — the API emits * `comment.created` for replies too, the catalog shape), else `created`. * - `comment.reopened` → `reopened` (additive internal event) * - `comment.reaction` → `reacted` (additive internal event) * - `comment.deleted` → `deleted` (additive internal event) * - `comment.resolved` → `resolved` (catalog event) * * The realtime server (the `comment-notification-source`) is the ONLY * consumer of this table; both the API (emitter) and the realtime app * (mapper) import it from here so the two layers cannot drift. */ import type { DomainEvent } from "../events.js"; /** The `comment.event` frame kinds (the frame schema's enum — lockstep). */ export const COMMENT_EVENT_KINDS = [ "created", "replied", "resolved", "reopened", "reacted", "deleted", ] as const; export type CommentEventKind = (typeof COMMENT_EVENT_KINDS)[number]; /** The stream event names that map to a comment frame (single source). */ export const COMMENT_EVENT_STREAM_NAMES = [ "comment.created", "comment.resolved", "comment.reopened", "comment.reaction", "comment.deleted", ] as const; export type CommentEventStreamName = (typeof COMMENT_EVENT_STREAM_NAMES)[number]; /** * Derive the `comment.event` frame kind from a stream event envelope. * `null` when the event does not map to a comment frame (any other event * name, or a malformed payload). * * The reply case is payload-driven: the API emits `comment.created` for * BOTH a new thread and a reply (a reply = a comment with a parent); the * reply payload carries `replyId` (the reply row id) + `commentId` (the * parent thread id). A `comment.created` WITHOUT a `replyId` is a new * thread → `created`. */ export function commentEventKindForEvent(event: DomainEvent): CommentEventKind | null { const payload = event.payload as Record | null | undefined; switch (event.name) { case "comment.created": { // A reply is a `comment.created` whose payload carries a `replyId`. return payload && typeof payload.replyId === "string" ? "replied" : "created"; } case "comment.resolved": return "resolved"; case "comment.reopened": return "reopened"; case "comment.reaction": return "reacted"; case "comment.deleted": return "deleted"; default: return null; } }