Remove archive/restore from the session menu and drop archived=1 listing. PATCH still accepts archived_at from old bundles then ignores it. A forward migration clears archived_at without bumping updated_at. Delete stays unchanged.
64 lines
2.1 KiB
TypeScript
64 lines
2.1 KiB
TypeScript
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);
|
|
}
|