This repository has no description
0

Configure Feed

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

stigmergic / container / server.ts
6.6 kB 184 lines
1// ─── Strata Analysis Container Server ──────────────────────────────── 2// HTTP server running inside the Cloudflare Container. 3// Endpoints: 4// POST /analyze — run strata analysis on an island 5// POST /deriveIsland — derive island (connected component) from a lexmin vertex 6// POST /sync — sync vault + carry data 7// GET /health — health check 8 9import { analyze } from "./analysis.ts"; 10import { syncAll, syncVault, syncCarry } from "./sync.ts"; 11import { createServer } from "http"; 12import { 13 execSync, 14} from "child_process"; 15 16const PORT = 8080; 17const CARRY_PATH = process.env.CARRY_PATH || "/data/carry"; 18 19createServer(async (req, res) => { 20 const url = new URL(req.url || "/", `http://localhost:${PORT}`); 21 22 // ── Health ── 23 if (url.pathname === "/health" && req.method === "GET") { 24 res.writeHead(200); 25 res.end("ok"); 26 return; 27 } 28 29 // ── Sync ── 30 if (url.pathname === "/sync" && req.method === "POST") { 31 try { 32 await syncAll(); 33 res.writeHead(200, { "Content-Type": "application/json" }); 34 res.end(JSON.stringify({ ok: true })); 35 } catch (e: any) { 36 res.writeHead(500, { "Content-Type": "application/json" }); 37 res.end(JSON.stringify({ error: e.message })); 38 } 39 return; 40 } 41 42 // ── Derive Islands ── 43 // Input: { connections: Array<{source, target, connectionType, note, did, rkey}>, seed?: string } 44 // - With seed: derive single island containing that vertex 45 // - Without seed: detect all islands (all connected components) 46 // Output: { islands: Array<{id, lexmin, vertices, edges}> } 47 if (url.pathname === "/deriveIsland" && req.method === "POST") { 48 const chunks: Buffer[] = []; 49 for await (const chunk of req) chunks.push(chunk); 50 const body = Buffer.concat(chunks).toString(); 51 52 try { 53 const input = JSON.parse(body); 54 const { connections, seed } = input; 55 56 if (!Array.isArray(connections)) { 57 res.writeHead(400, { "Content-Type": "application/json" }); 58 res.end(JSON.stringify({ error: "connections[] required" })); 59 return; 60 } 61 62 // Build adjacency list 63 const adj = new Map<string, Array<typeof connections[0]>>(); 64 const allVertices = new Set<string>(); 65 for (const conn of connections) { 66 const s = conn.source as string; 67 const t = conn.target as string; 68 if (!adj.has(s)) adj.set(s, []); 69 if (!adj.has(t)) adj.set(t, []); 70 adj.get(s)!.push(conn); 71 adj.get(t)!.push(conn); 72 allVertices.add(s); 73 allVertices.add(t); 74 } 75 76 // BFS from a seed vertex to find one connected component 77 function bfsComponent(start: string): { 78 vertices: string[]; 79 edges: any[]; 80 } { 81 const visited = new Set<string>(); 82 const queue = [start]; 83 const componentEdges: any[] = []; 84 85 while (queue.length > 0) { 86 const current = queue.shift()!; 87 if (visited.has(current)) continue; 88 visited.add(current); 89 90 for (const conn of adj.get(current) || []) { 91 const s = conn.source as string; 92 const t = conn.target as string; 93 componentEdges.push({ 94 uri: `at://${conn.did}/network.cosmik.connection/${conn.rkey}`, 95 did: conn.did, 96 rkey: conn.rkey, 97 source: s, 98 target: t, 99 connectionType: conn.connectionType || "relates", 100 note: conn.note || "", 101 }); 102 if (!visited.has(s)) queue.push(s); 103 if (!visited.has(t)) queue.push(t); 104 } 105 } 106 107 // Dedupe edges 108 const vertexSet = new Set(visited); 109 const edges = componentEdges.filter(e => vertexSet.has(e.source) && vertexSet.has(e.target)); 110 const seenEdges = new Set<string>(); 111 const dedupedEdges = edges.filter(e => { 112 const key = `${e.source}|${e.target}|${e.rkey}`; 113 if (seenEdges.has(key)) return false; 114 seenEdges.add(key); 115 return true; 116 }); 117 118 const sortedVertices = [...visited].sort(); 119 return { vertices: sortedVertices, edges: dedupedEdges }; 120 } 121 122 const { createHash } = await import("crypto"); 123 124 if (seed) { 125 // Single island from seed vertex 126 const { vertices, edges } = bfsComponent(seed); 127 const lexmin = vertices[0]; 128 const id = createHash("sha256").update(lexmin).digest("hex").slice(0, 16); 129 res.writeHead(200, { "Content-Type": "application/json" }); 130 res.end(JSON.stringify({ islands: [{ id, lexmin, vertices, edges }] })); 131 } else { 132 // All islands — find all connected components 133 const globalVisited = new Set<string>(); 134 const islands: any[] = []; 135 136 for (const vertex of allVertices) { 137 if (globalVisited.has(vertex)) continue; 138 const { vertices, edges } = bfsComponent(vertex); 139 for (const v of vertices) globalVisited.add(v); 140 const lexmin = vertices[0]; 141 const id = createHash("sha256").update(lexmin).digest("hex").slice(0, 16); 142 islands.push({ id, lexmin, vertices, edges }); 143 } 144 145 // Sort by vertex count descending 146 islands.sort((a, b) => b.vertices.length - a.vertices.length); 147 148 res.writeHead(200, { "Content-Type": "application/json" }); 149 res.end(JSON.stringify({ islands })); 150 } 151 } catch (e: any) { 152 console.error("deriveIsland error:", e); 153 res.writeHead(500, { "Content-Type": "application/json" }); 154 res.end(JSON.stringify({ error: e.message })); 155 } 156 return; 157 } 158 159 // ── Analyze ── 160 if (url.pathname === "/analyze" && req.method === "POST") { 161 const chunks: Buffer[] = []; 162 for await (const chunk of req) chunks.push(chunk); 163 const body = Buffer.concat(chunks).toString(); 164 165 try { 166 const input = JSON.parse(body); 167 const result = await analyze(input); 168 res.writeHead(200, { "Content-Type": "application/json" }); 169 res.end(JSON.stringify(result)); 170 } catch (e: any) { 171 console.error("Analysis error:", e); 172 res.writeHead(500, { "Content-Type": "application/json" }); 173 res.end(JSON.stringify({ error: e.message })); 174 } 175 return; 176 } 177 178 res.writeHead(404); 179 res.end("Not found"); 180}).listen(PORT, () => { 181 console.log(`Strata analysis container listening on port ${PORT}`); 182 // Sync on startup 183 syncAll().catch(e => console.error("Initial sync failed:", e)); 184});