Code Inbox

model.js 199 lines

Files · 8
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199import ddl from "./schema.js";
import manifest from "./manifest.json";
import { database } from "../../shared/database.js";
// Project only the supported GitHub REST response fields. Keep IDs and URLs intact.
export function fromGitHub(item) {
  if (!item || typeof item !== "object" || Array.isArray(item))
    throw Error("Each notification must be an object.");
  if (typeof item.id !== "string")
    throw Error("GitHub notification IDs must be strings.");
  if (item.unread !== undefined && typeof item.unread !== "boolean")
    throw Error("GitHub unread must be a boolean.");
  return {
    id: item.id,
    repository_full_name: item.repository?.full_name,
    repository_html_url: item.repository?.html_url ?? null,
    subject_title: item.subject?.title,
    subject_type: item.subject?.type,
    subject_url: item.subject?.url ?? null,
    subject_latest_comment_url: item.subject?.latest_comment_url ?? null,
    reason: item.reason,
    unread: item.unread === undefined ? 1 : Number(item.unread),
    updated_at: item.updated_at,
    last_read_at: item.last_read_at ?? null,
    url: item.url,
    subscription_url: item.subscription_url ?? null,
  };
}
export function createCodeInbox(L) {
  const db = database(L, ddl, manifest);
  const normalize = (data) => {
    db.check("receive-thread", data);
    for (const field of [
      "id",
      "repository_full_name",
      "subject_title",
      "subject_type",
      "reason",
      "updated_at",
      "url",
    ])
      if (!data[field]?.trim()) throw Error(`${field} cannot be empty.`);
    for (const field of ["updated_at", "last_read_at"])
      if (
        data[field] != null &&
        (!/^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}(?:\.\d+)?(?:Z|[+-]\d{2}:\d{2})$/.test(
          data[field],
        ) ||
          !Number.isFinite(Date.parse(data[field])))
      )
        throw Error(`${field} must be an ISO timestamp.`);
    return data;
  };
  const save = async (data, existing) => {
    const row = existing
      ? await db.update("threads", data.id, data)
      : await db.insert("threads", data);
    await db.emit(existing ? "thread-updated" : "thread-received", row);
    return row;
  };
  return {
    ...db,
    list: () =>
      db.ready.then(() =>
        db.query(
          "SELECT * FROM threads ORDER BY julianday(updated_at) DESC, id",
        ),
      ),
    receive: (data) =>
      db.run(async () => {
        normalize(data);
        if (await db.get("threads", data.id)) return;
        return save(data, false);
      }),
    save: (data, id) =>
      db.run(async () => {
        normalize(data);
        if (id !== undefined && data.id !== id)
          throw Error("A thread keeps its source ID.");
        const existing = await db.get("threads", data.id);
        if (id === undefined && existing)
          throw Error(
            "That thread ID already exists. Edit it or import an update.",
          );
        if (id !== undefined && !existing)
          throw Error("Thread no longer exists.");
        const row = await save(data, !!existing);
        if (existing?.unread && row.unread === 0)
          await db.emit("thread-read", row);
        return row;
      }),
    import: (items) =>
      db.run(async () => {
        const list = (Array.isArray(items) ? items : [items])
          .map(fromGitHub)
          .map(normalize);
        // Validate every record before writing, so malformed input never partially imports.
        for (const data of list) {
          const existing = await db.get("threads", data.id);
          if (
            existing &&
            Object.entries(data).every(([k, v]) => existing[k] === v)
          )
            continue;
          await save(data, !!existing);
        }
        return list.length;
      }),
    read: (id) =>
      db.run(async () => {
        const row = await db.get("threads", id);
        if (!row || !row.unread) return;
        const updated = await db.update("threads", id, {
          unread: 0,
          last_read_at: new Date().toISOString(),
        });
        await db.emit("thread-read", updated);
      }),
    remove: (id) =>
      db.run(() => db.exec("DELETE FROM threads WHERE id = ?", [id])),
  };
}
export const samples = [
  {
    id: "sample-851",
    repository: {
      full_name: "northstar/design-system",
      html_url: "https://example.invalid/resource",
    },
    subject: {
      title: "Add keyboard navigation to the command menu",
      type: "PullRequest",
      url: "https://example.invalid/resource",
      latest_comment_url: null,
    },
    reason: "review_requested",
    unread: true,
    updated_at: "2026-10-15T10:42:00Z",
    last_read_at: null,
    url: "https://example.invalid/resource",
    subscription_url: null,
  },
  {
    id: "sample-852",
    repository: {
      full_name: "northstar/mobile",
      html_url: null,
    },
    subject: {
      title: "Offline changes disappear after reconnecting",
      type: "Issue",
      url: null,
      latest_comment_url: null,
    },
    reason: "mention",
    unread: true,
    updated_at: "2026-10-15T09:20:00Z",
    last_read_at: null,
    url: "https://example.invalid/resource",
    subscription_url: null,
  },
  {
    id: "sample-853",
    repository: {
      full_name: "northstar/design-system",
      html_url: null,
    },
    subject: {
      title: "Version 2.4.0",
      type: "Release",
      url: null,
      latest_comment_url: null,
    },
    reason: "subscribed",
    unread: false,
    updated_at: "2026-10-14T16:10:00Z",
    last_read_at: "2026-10-14T17:00:00Z",
    url: "https://example.invalid/resource",
    subscription_url: null,
  },
  {
    id: "sample-854",
    repository: {
      full_name: "northstar/docs",
      html_url: null,
    },
    subject: {
      title: "Document the new import format",
      type: "Issue",
      url: null,
      latest_comment_url: null,
    },
    reason: "assign",
    unread: true,
    updated_at: "2026-10-14T14:35:00Z",
    last_read_at: null,
    url: "https://example.invalid/resource",
    subscription_url: null,
  },
];