84 lines · 3.1 KB
import { chunks, MAX_IN_LIST, placeholders } from "./db";
import { repoDb } from "./scoped-db";
import { locate } from "./scoped-db/locators";
import { REACTION_CONTENTS } from "~/lib/reactions";
import type { ReactionContent, ReactionSubject, ReactionSummary } from "~/lib/reactions";

function isReaction(value: string): value is ReactionContent {
  return REACTION_CONTENTS.some((content) => content === value);
}

/** Groups subject ids by the repository that holds them, via the locator directory. */
async function byRepo(subjectType: ReactionSubject, ids: string[]): Promise<Map<string, string[]>> {
  const grouped = new Map<string, string[]>();
  for (const id of ids) {
    const repoId = await locate(subjectType, id);
    if (!repoId) continue;
    grouped.set(repoId, [...(grouped.get(repoId) ?? []), id]);
  }
  return grouped;
}

/**
 * Aggregated reactions per subject id, with whether the viewer has reacted. Pass `repoId` when every subject is in
 * that repository (the usual case: one thread); otherwise each subject's repository is looked up.
 */
export async function reactionSummaries(
  subjectType: ReactionSubject,
  ids: string[],
  viewerId: string | null,
  repoId?: string,
): Promise<Map<string, ReactionSummary[]>> {
  const out = new Map<string, ReactionSummary[]>();
  const unique = [...new Set(ids)];
  if (unique.length === 0) return out;
  const groups = repoId ? new Map([[repoId, unique]]) : await byRepo(subjectType, unique);
  for (const [scope, part] of [...groups].flatMap(([scope, list]) =>
    chunks(list, MAX_IN_LIST - 2).map((chunk) => [scope, chunk] as const),
  )) {
    const rows = await repoDb(scope).all<{
      subject_id: string;