feat(m8): SSE hub + realtime chat with mentions, tests, e2e

- SseHub (project-scoped client mgmt + broadcast, implements SseBroadcaster)
  with SSE-formatted payload; singleton getSseHub
- ChatRepository (message CRUD, soft-delete, search, pagination, project
  isolation) + ChatService (membership permission, send/edit/delete,
  @email mention -> mention notification, SSE broadcast of created/updated/deleted)
- SSE stream endpoint (ReadableStream + heartbeat + abort cleanup, membership)
- Chat message API routes (history/send/edit/delete)
- ChatWindow client (EventSource auto-reconnect, history fetch, realtime
  append/update/delete), chat page, ProjectNav チャット link
- SseEvent type now carries ChatMessage for chat events
- Unit (SseHub, ChatRepository, ChatService) + integration chat-sse-broadcast
  + e2e chat-sse (two contexts realtime)
This commit is contained in:
Ken Yasue
2026-06-25 01:45:34 +02:00
parent 83a02265f5
commit ddad5ae78c
16 changed files with 1183 additions and 7 deletions

View File

@ -277,16 +277,25 @@ export type NotificationEvent =
projectMemberIds: number[];
});
/** SSE配信イベント(M8でSseHubが扱う)。M5ではbroadcasterの型として使用 */
/** SSE配信イベント(M8でSseHubが扱う)。 */
export type SseEvent =
| { type: 'notification.created'; data: { projectId: number | null } }
| { type: 'chat.message.created'; data: { projectId: number } }
| { type: 'chat.message.updated'; data: { projectId: number } }
| { type: 'chat.message.deleted'; data: { projectId: number; id: number } }
| {
type: 'chat.message.created';
data: { projectId: number; message: ChatMessage };
}
| {
type: 'chat.message.updated';
data: { projectId: number; message: ChatMessage };
}
| {
type: 'chat.message.deleted';
data: { projectId: number; id: number };
}
| { type: 'todo.updated'; data: { projectId: number } }
| { type: 'file.uploaded'; data: { projectId: number } }
| { type: 'meeting.created'; data: { projectId: number } }
| { type: 'note.updated'; data: { projectId: number } };
| { type: 'note.updated'; data: { projectId: number } }
| { type: 'notification.created'; data: { projectId: number | null } };
/** SSE配信を行うコンポーネントの抽象(M8のSseHubが実装) */
export interface SseBroadcaster {