import { BadRequestException } from '@nestjs/common'; /** * Keyset cursor for paging a thread backwards from newest. * * The sort key is the pair `(createdAt, id)`, not `createdAt` alone: two * messages can share a millisecond, and a cursor on a non-unique key either * re-serves or skips the tied rows depending which side of the boundary they * land on. The id breaks ties with a stable total order. * * Deliberately a twin of the freight API's `message-cursor.ts`, not a shared * import: the two APIs share no runtime package, and @edr/types is Nest-free by * design (this throws Nest exceptions). The wire format matches so a client can * treat both chats identically — keep them in step if either changes. */ export interface MessageCursor { createdAt: Date; id: string; } export function encodeMessageCursor(cursor: MessageCursor): string { return Buffer.from(`${cursor.createdAt.toISOString()}|${cursor.id}`, 'utf8').toString( 'base64url', ); } /** * Parse a client-supplied cursor. Rejects anything malformed rather than * silently falling back to "first page" — a corrupted cursor that degrades to * page 1 makes an infinite scroll loop forever over the same rows. */ export function decodeMessageCursor(raw: string): MessageCursor { let decoded: string; try { decoded = Buffer.from(raw, 'base64url').toString('utf8'); } catch { throw new BadRequestException('Malformed pagination cursor.'); } const separator = decoded.lastIndexOf('|'); if (separator === -1) { throw new BadRequestException('Malformed pagination cursor.'); } const createdAt = new Date(decoded.slice(0, separator)); const id = decoded.slice(separator + 1); if (Number.isNaN(createdAt.getTime()) || !id) { throw new BadRequestException('Malformed pagination cursor.'); } return { createdAt, id }; }