import { SmrtCollection } from '@happyvertical/smrt-core'; import { ChatMessage } from '../models/ChatMessage.js'; import { ChatMessageSearchFilters } from '../types.js'; export declare class ChatMessageCollection extends SmrtCollection { static readonly _itemClass: typeof ChatMessage; /** * Get messages for a room (newest first), excluding threads and deleted. * * Filtering, ordering and pagination are pushed into SQL rather than loading * the full room history into memory (S5 #1392, DoS hardening). Thread replies * are excluded via `threadId IS NULL` (the WHERE API maps a `null` value to * `IS NULL`). * * `tenantId` is REQUIRED and bound into BOTH the message window AND the cursor * lookup (S5 #1392): room ids are not globally unique, so a roomId-only read * could surface another tenant's messages for a same-id room. The caller's * membership gate is tenant-scoped, so the read MUST be too. */ getByRoom(roomId: string, tenantId: string, options?: { limit?: number; before?: string; }): Promise; /** * Get messages in a thread, tenant-bound (S5 #1392). * * `tenantId` is bound into the query so a thread id from another tenant can * never surface that tenant's thread history. */ getByThread(threadId: string, tenantId: string): Promise; /** * Get messages for an agent session, tenant-bound (S5 #1392). * * `tenantId` is bound into the query so a session id from another tenant can * never surface that tenant's session messages. */ getByAgentSession(agentSessionId: string, tenantId: string): Promise; /** * Search messages with filters. * * All structural filters (tenant/room/thread/sender/type/role/date/text) are * pushed into SQL instead of loading the full message set and filtering in JS * (S5 #1392, DoS hardening). `tenantId` is REQUIRED and always bound so the * search can never cross a tenant boundary. * * `hasAttachments` is derived from the stored `attachments` JSON column and is * pushed into SQL as a `!=`/`=` pre-filter against the empty-array sentinel * (`'[]'`, the column default) so the `LIMIT` applies to the ALREADY-FILTERED * set, not the raw window (S5 #1392). Previously the limit truncated the * newest-N window first and the JS filter ran afterwards, so a room whose * newest messages lacked attachments could return fewer (or zero) * attachment-bearing rows than existed. A precise JS pass on * `hasAttachments()` still runs to reject any malformed/empty column value the * coarse SQL sentinel can't distinguish, and the requested `limit` is enforced * on the filtered result. */ search(filters: ChatMessageSearchFilters & { tenantId: string; limit?: number; }): Promise; /** * Get unread count for a participant in a room (excludes thread replies). * * Counting is pushed into SQL via `count()` rather than loading the whole * room history into memory (S5 #1392, DoS hardening). `tenantId` is REQUIRED * and bound into both the count and the read-cursor lookup so the count can * never include another tenant's messages for a same-id room. */ getUnreadCount(roomId: string, tenantId: string, lastReadMessageId: string | null): Promise; /** * Get most recent root message for each room (for room list preview). * * Each room is fetched with `orderBy created_at DESC, limit 1` so we never * load the full per-room history into memory (S5 #1392, DoS hardening). * `tenantId` is REQUIRED and bound into each per-room read so a same-id room * from another tenant can never leak a preview message. */ getLatestPerRoom(roomIds: string[], tenantId: string): Promise>; } //# sourceMappingURL=ChatMessageCollection.d.ts.map