UNPKG

@wavequery/conductor

Version:
231 lines 8.06 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.VizServer = void 0; const ws_1 = require("ws"); const http_1 = require("http"); const events_1 = require("events"); const client_1 = require("./templates/client"); const constants_1 = require("./constants"); class VizServer extends events_1.EventEmitter { constructor(port = 3000, config = {}) { super(); this.clients = new Set(); this.currentGraph = { nodes: [], edges: [] }; this.config = { theme: "light", fitView: true, ...constants_1.DEFAULT_CONFIG, ...config, }; this.httpServer = (0, http_1.createServer)((req, res) => { if (req.url === "/") { res.writeHead(200, { "Content-Type": "text/html" }); res.end(this.getClientHtml()); } }); this.wss = new ws_1.WebSocketServer({ server: this.httpServer }); this.setupWebSocket(); this.httpServer.listen(port); } setupWebSocket() { this.wss.on("connection", (ws) => { this.clients.add(ws); this.sendToClient(ws, { type: "init", payload: { graph: this.currentGraph, metadata: this.getGraphMetadata(), }, }); ws.on("message", (message) => { try { const data = JSON.parse(message); this.handleClientMessage(data, ws); } catch (error) { console.error("Error handling message:", error); } }); ws.on("close", () => { this.clients.delete(ws); }); }); } handleClientMessage(message, client) { switch (message.type) { case "nodeClick": this.emit("nodeClick", message.payload); break; case "edgeClick": this.emit("edgeClick", message.payload); break; } } getGraphMetadata() { return { timestamp: Date.now(), totalNodes: this.currentGraph.nodes.length, nodesByType: this.getNodesByType(), nodesByStatus: this.getNodesByStatus(), }; } getNodesByType() { return this.currentGraph.nodes.reduce((acc, node) => { acc[node.type] = (acc[node.type] || 0) + 1; return acc; }, {}); } getNodesByStatus() { return this.currentGraph.nodes.reduce((acc, node) => { const status = node.status || "pending"; acc[status] = (acc[status] || 0) + 1; return acc; }, {}); } sendToClient(client, message) { if (client.readyState === ws_1.WebSocket.OPEN) { client.send(JSON.stringify(message)); } } broadcast(message) { const messageStr = JSON.stringify(message); this.clients.forEach((client) => { if (client.readyState === ws_1.WebSocket.OPEN) { client.send(messageStr); } }); } getClientHtml() { const address = this.httpServer.address(); if (!address) throw new Error("Server address is not available."); const host = address.address === "::" ? "localhost" : address.address; return (0, client_1.getClientTemplate)(`${host}:${address.port}`, this.config); } processGraph(graph) { const processedGraph = JSON.parse(JSON.stringify(graph)); processedGraph.nodes = processedGraph.nodes.map((node) => ({ ...node, // Only set initial positions if not already set x: node.x ?? Math.random() * 800, // Random initial position y: node.y ?? Math.random() * 600, // Maintain fixed positions for pinned nodes fx: node.fixed ? (node.x ?? 400) : undefined, fy: node.fixed ? (node.y ?? 300) : undefined, data: { ...node.data, createdAt: node.data?.createdAt || new Date().toISOString(), }, })); // Process edges - ensure proper source/target references processedGraph.edges = processedGraph.edges.map((edge) => ({ ...edge, id: edge.id, source: typeof edge.source === "string" ? edge.source : edge.source.id, target: typeof edge.target === "string" ? edge.target : edge.target.id, data: { ...edge.data, createdAt: edge.data?.createdAt || new Date().toISOString(), type: edge.type, }, })); return processedGraph; } pinNode(nodeId, position) { const node = this.currentGraph.nodes.find((n) => n.id === nodeId); if (node) { node.fixed = true; if (position) { node.x = node.fx = position.x; node.y = node.fy = position.y; } else { node.fx = node.x; node.fy = node.y; } this.broadcast({ type: "node-update", payload: { nodeId, updates: node }, }); } } unpinNode(nodeId) { const node = this.currentGraph.nodes.find((n) => n.id === nodeId); if (node) { node.fixed = false; node.fx = node.fy = undefined; this.broadcast({ type: "node-update", payload: { nodeId, updates: node }, }); } } mergeGraphs(currentGraph, updates) { const merged = { nodes: [...currentGraph.nodes], edges: [...currentGraph.edges], }; if (updates.nodes) { updates.nodes.forEach((newNode) => { const existingIndex = merged.nodes.findIndex((n) => n.id === newNode.id); if (existingIndex >= 0) { // Update existing node while preserving position if already set merged.nodes[existingIndex] = { ...merged.nodes[existingIndex], ...newNode, x: merged.nodes[existingIndex].x ?? newNode.x, y: merged.nodes[existingIndex].y ?? newNode.y, }; } else { merged.nodes.push(newNode); } }); } if (updates.edges) { updates.edges.forEach((newEdge) => { const existingIndex = merged.edges.findIndex((e) => e.id === newEdge.id); if (existingIndex >= 0) { merged.edges[existingIndex] = newEdge; } else { merged.edges.push(newEdge); } }); } return this.processGraph(merged); } updateGraph(updates) { this.currentGraph = this.mergeGraphs(this.currentGraph, updates); this.broadcast({ type: "graph-update", payload: { graph: this.currentGraph, metadata: this.getGraphMetadata(), }, }); } updateNode(nodeId, updates) { const nodeIndex = this.currentGraph.nodes.findIndex((n) => n.id === nodeId); if (nodeIndex !== -1) { this.currentGraph.nodes[nodeIndex] = { ...this.currentGraph.nodes[nodeIndex], ...updates, }; this.broadcast({ type: "node-update", payload: { nodeId, updates, metadata: this.getGraphMetadata(), }, }); } } close() { this.wss.close(); this.httpServer.close(); } } exports.VizServer = VizServer; //# sourceMappingURL=server.js.map