/** * Shared island utilities: canonical ID, component types, BFS, DB access. */ import { resolve } from 'node:path'; import { createHash } from 'node:crypto'; import { Database } from 'bun:sqlite'; const CARRY_DIR = process.env.CARRY_DIR ?? '/home/nandi/.local/share/carry-vault'; const DB_PATH = process.env.ISLAND_DETECT_DB ?? resolve(CARRY_DIR, '.island-detect.db'); export interface Component { nodes: Set; dids: Set; lexmin: string; islandId: string; } export interface EdgeRow { source: string; target: string; relation: string | null; did: string; } // --- Canonical island ID: first 12 hex chars of SHA-256(lexmin) --- export function islandId(lexmin: string): string { return createHash('sha256').update(lexmin).digest('hex').slice(0, 12); } // --- SQLite --- export function openDb(): Database { return new Database(DB_PATH, { readonly: true }); } // --- Connected components (BFS) --- export function findComponents(db: Database): Component[] { const adj = new Map>(); const edgeDids = new Map>(); const rows = db.query('SELECT source, target, did FROM edges').all() as Array<{ source: string; target: string; did: string }>; for (const row of rows) { const s = row.source; const t = row.target; if (!adj.has(s)) adj.set(s, new Set()); if (!adj.has(t)) adj.set(t, new Set()); adj.get(s)!.add(t); adj.get(t)!.add(s); if (!edgeDids.has(s)) edgeDids.set(s, new Set()); if (!edgeDids.has(t)) edgeDids.set(t, new Set()); edgeDids.get(s)!.add(row.did); edgeDids.get(t)!.add(row.did); } const visited = new Set(); const components: Component[] = []; for (const node of adj.keys()) { if (visited.has(node)) continue; const compNodes = new Set(); const compDids = new Set(); const queue = [node]; while (queue.length > 0) { const current = queue.pop()!; if (visited.has(current)) continue; visited.add(current); compNodes.add(current); for (const did of edgeDids.get(current) ?? []) compDids.add(did); for (const neighbor of adj.get(current) ?? []) { if (!visited.has(neighbor)) queue.push(neighbor); } } const lexmin = [...compNodes].sort()[0]; components.push({ nodes: compNodes, dids: compDids, lexmin, islandId: islandId(lexmin) }); } return components.sort((a, b) => b.nodes.size - a.nodes.size); } /** Find a component by its island ID (lexmin hash). */ export function findComponentById(db: Database, targetId: string): Component | undefined { return findComponents(db).find((c) => c.islandId === targetId); } /** Fetch edges for a component from the DB. */ export function fetchComponentEdges(db: Database, component: Component): EdgeRow[] { const nodePlaceholders = [...component.nodes].map(() => '?').join(','); const nodeValues = [...component.nodes]; const edges = db.query( `SELECT source, target, relation, did FROM edges WHERE source IN (${nodePlaceholders}) OR target IN (${nodePlaceholders})`, ).all(...nodeValues, ...nodeValues) as EdgeRow[]; // Deduplicate (same source+target from multiple DIDs) const seen = new Set(); const unique: EdgeRow[] = []; for (const e of edges) { const key = `${e.source}::${e.target}`; if (!seen.has(key)) { seen.add(key); unique.push(e); } } return unique; } /** Resolve handles for a set of DIDs. */ export function resolveDidHandles(db: Database, dids: Set): Map { const handles = new Map(); if (dids.size === 0) return handles; const placeholders = [...dids].map(() => '?').join(','); const rows = db.query(`SELECT did, handle FROM dids WHERE did IN (${placeholders})`).all(...dids) as Array<{ did: string; handle: string | null }>; for (const r of rows) handles.set(r.did, r.handle); return handles; } /** Extract domain from a URL for display. */ export function domainFromUrl(url: string): string { try { return new URL(url).hostname.replace(/^www\./, ''); } catch { return url; } }