const sessionIdPattern = /^[0-9a-f]{8}-[0-9a-f]{4}-[1-8][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i; export const SESSION_PAGE_SIZE = 40; export const SESSION_PAGE_LIMIT_MIN = 1; export const SESSION_PAGE_LIMIT_MAX = 100; export type SessionCursor = { readonly updatedAt: string; readonly id: string; }; export function clampSessionLimit(raw: string | null | undefined): number { if (raw == null || raw === "") return SESSION_PAGE_SIZE; const parsed = Number.parseInt(raw, 10); if (!Number.isFinite(parsed)) return SESSION_PAGE_SIZE; return Math.min(SESSION_PAGE_LIMIT_MAX, Math.max(SESSION_PAGE_LIMIT_MIN, Math.trunc(parsed))); } export function encodeSessionCursor(updatedAt: string, id: string): string { return `${updatedAt},${id}`; } export function parseSessionCursor(raw: string | null | undefined): SessionCursor | null { if (!raw) return null; const comma = raw.indexOf(","); if (comma <= 0 || comma === raw.length - 1) return null; const updatedAt = raw.slice(0, comma); const id = raw.slice(comma + 1); if (!Number.isFinite(Date.parse(updatedAt))) return null; if (!sessionIdPattern.test(id)) return null; return { updatedAt, id }; } export function sessionCursorFilter(cursor: SessionCursor): string { const updatedAt = JSON.stringify(cursor.updatedAt); const id = JSON.stringify(cursor.id); return `updated_at.lt.${updatedAt},and(updated_at.eq.${updatedAt},id.lt.${id})`; } export function compareSessionCursor( left: SessionCursor, right: SessionCursor, ): number { const time = Date.parse(right.updatedAt) - Date.parse(left.updatedAt); if (time !== 0) return time; return left.id < right.id ? 1 : left.id > right.id ? -1 : 0; } export function nextSessionCursor( rows: readonly { id: string; updated_at?: string; updatedAt?: string }[], limit: number, ): string | null { if (rows.length <= limit) return null; const last = rows[limit - 1]; if (!last) return null; const updatedAt = typeof last.updated_at === "string" ? last.updated_at : typeof last.updatedAt === "string" ? last.updatedAt : ""; if (!updatedAt) return null; return encodeSessionCursor(updatedAt, last.id); }