/** * Types and guards the rows of `notifications`, and converts them to the * {@link Notification} that `GET /api/notifications` answers. * * @module */ import type { SQLOutputValue } from "node:sqlite"; import type { Notification, NotificationData, NotificationType, } from "../../shared/notification.ts"; /** * Stored by the triggers of `notification-service.ts`, and read back by * `getNotificationsForUser`. */ export interface NotificationRow { id: string; user_id: string; /** A {@link NotificationType}, not checked by the guard. */ type: string; /** The {@link NotificationData} as JSON, not checked by the guard. */ data: string; /** 0 or 1. */ read: number; created_at: string; /** * Names the event that caused the notification. A unique index keeps a * second row with the same `(user_id, source_key)` out; null allows * duplicates. */ source_key: string | null; [key: string]: SQLOutputValue; } /** * Checks whether `obj`, a row `getNotificationsForUser` selected, is a * {@link NotificationRow}. */ export function isNotificationRow( obj: unknown, ): obj is NotificationRow { return !!obj && typeof obj === "object" && "id" in obj && typeof obj.id === "string" && "user_id" in obj && typeof obj.user_id === "string" && "type" in obj && typeof obj.type === "string" && "data" in obj && typeof obj.data === "string" && "read" in obj && typeof obj.read === "number" && "created_at" in obj && typeof obj.created_at === "string" && "source_key" in obj && (typeof obj.source_key === "string" || obj.source_key === null); } /** * Converts a row to the {@link Notification} the client gets. * * @throws {SyntaxError} When `data` isn't valid JSON. */ export function notificationRowToApi(row: NotificationRow): Notification { return { id: row.id, userId: row.user_id, type: row.type as NotificationType, data: JSON.parse(row.data) as NotificationData, read: Boolean(row.read), createdAt: new Date(row.created_at), }; }