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