This repository has no description
0

Configure Feed

Select the types of activity you want to include in your feed.

stigmergic / src / island-shared.ts
4.1 kB 126 lines
1/** 2 * Shared island utilities: canonical ID, component types, BFS, DB access. 3 */ 4 5import { resolve } from 'node:path'; 6import { createHash } from 'node:crypto'; 7import { Database } from 'bun:sqlite'; 8 9const CARRY_DIR = process.env.CARRY_DIR ?? '/home/nandi/.local/share/carry-vault'; 10const DB_PATH = process.env.ISLAND_DETECT_DB ?? resolve(CARRY_DIR, '.island-detect.db'); 11 12export interface Component { 13 nodes: Set<string>; 14 dids: Set<string>; 15 lexmin: string; 16 islandId: string; 17} 18 19export interface EdgeRow { 20 source: string; 21 target: string; 22 relation: string | null; 23 did: string; 24} 25 26// --- Canonical island ID: first 12 hex chars of SHA-256(lexmin) --- 27 28export function islandId(lexmin: string): string { 29 return createHash('sha256').update(lexmin).digest('hex').slice(0, 12); 30} 31 32// --- SQLite --- 33 34export function openDb(): Database { 35 return new Database(DB_PATH, { readonly: true }); 36} 37 38// --- Connected components (BFS) --- 39 40export function findComponents(db: Database): Component[] { 41 const adj = new Map<string, Set<string>>(); 42 const edgeDids = new Map<string, Set<string>>(); 43 44 const rows = db.query('SELECT source, target, did FROM edges').all() as Array<{ source: string; target: string; did: string }>; 45 for (const row of rows) { 46 const s = row.source; 47 const t = row.target; 48 if (!adj.has(s)) adj.set(s, new Set()); 49 if (!adj.has(t)) adj.set(t, new Set()); 50 adj.get(s)!.add(t); 51 adj.get(t)!.add(s); 52 if (!edgeDids.has(s)) edgeDids.set(s, new Set()); 53 if (!edgeDids.has(t)) edgeDids.set(t, new Set()); 54 edgeDids.get(s)!.add(row.did); 55 edgeDids.get(t)!.add(row.did); 56 } 57 58 const visited = new Set<string>(); 59 const components: Component[] = []; 60 61 for (const node of adj.keys()) { 62 if (visited.has(node)) continue; 63 const compNodes = new Set<string>(); 64 const compDids = new Set<string>(); 65 const queue = [node]; 66 while (queue.length > 0) { 67 const current = queue.pop()!; 68 if (visited.has(current)) continue; 69 visited.add(current); 70 compNodes.add(current); 71 for (const did of edgeDids.get(current) ?? []) compDids.add(did); 72 for (const neighbor of adj.get(current) ?? []) { 73 if (!visited.has(neighbor)) queue.push(neighbor); 74 } 75 } 76 const lexmin = [...compNodes].sort()[0]; 77 components.push({ nodes: compNodes, dids: compDids, lexmin, islandId: islandId(lexmin) }); 78 } 79 80 return components.sort((a, b) => b.nodes.size - a.nodes.size); 81} 82 83/** Find a component by its island ID (lexmin hash). */ 84export function findComponentById(db: Database, targetId: string): Component | undefined { 85 return findComponents(db).find((c) => c.islandId === targetId); 86} 87 88/** Fetch edges for a component from the DB. */ 89export function fetchComponentEdges(db: Database, component: Component): EdgeRow[] { 90 const nodePlaceholders = [...component.nodes].map(() => '?').join(','); 91 const nodeValues = [...component.nodes]; 92 const edges = db.query( 93 `SELECT source, target, relation, did FROM edges WHERE source IN (${nodePlaceholders}) OR target IN (${nodePlaceholders})`, 94 ).all(...nodeValues, ...nodeValues) as EdgeRow[]; 95 96 // Deduplicate (same source+target from multiple DIDs) 97 const seen = new Set<string>(); 98 const unique: EdgeRow[] = []; 99 for (const e of edges) { 100 const key = `${e.source}::${e.target}`; 101 if (!seen.has(key)) { 102 seen.add(key); 103 unique.push(e); 104 } 105 } 106 return unique; 107} 108 109/** Resolve handles for a set of DIDs. */ 110export function resolveDidHandles(db: Database, dids: Set<string>): Map<string, string | null> { 111 const handles = new Map<string, string | null>(); 112 if (dids.size === 0) return handles; 113 const placeholders = [...dids].map(() => '?').join(','); 114 const rows = db.query(`SELECT did, handle FROM dids WHERE did IN (${placeholders})`).all(...dids) as Array<{ did: string; handle: string | null }>; 115 for (const r of rows) handles.set(r.did, r.handle); 116 return handles; 117} 118 119/** Extract domain from a URL for display. */ 120export function domainFromUrl(url: string): string { 121 try { 122 return new URL(url).hostname.replace(/^www\./, ''); 123 } catch { 124 return url; 125 } 126}