{"version":3,"file":"graph_mermaid.cjs","names":["toBase64Url"],"sources":["../../src/runnables/graph_mermaid.ts"],"sourcesContent":["import { Edge, Node } from \"./types.js\";\nimport { toBase64Url } from \"./utils.js\";\n\nfunction _escapeNodeLabel(nodeLabel: string): string {\n  // Escapes the node label for Mermaid syntax.\n  return nodeLabel.replace(/[^a-zA-Z-_0-9]/g, \"_\");\n}\n\nconst MARKDOWN_SPECIAL_CHARS = [\"*\", \"_\", \"`\"];\n\nfunction _generateMermaidGraphStyles(\n  nodeColors: Record<string, string>\n): string {\n  let styles = \"\";\n  for (const [className, color] of Object.entries(nodeColors)) {\n    styles += `\\tclassDef ${className} ${color};\\n`;\n  }\n  return styles;\n}\n\n/**\n * Draws a Mermaid graph using the provided graph data\n */\nexport function drawMermaid(\n  nodes: Record<string, Node>,\n  edges: Edge[],\n  config?: {\n    firstNode?: string;\n    lastNode?: string;\n    curveStyle?: string;\n    withStyles?: boolean;\n    nodeColors?: Record<string, string>;\n    wrapLabelNWords?: number;\n  }\n): string {\n  const {\n    firstNode,\n    lastNode,\n    nodeColors,\n    withStyles = true,\n    curveStyle = \"linear\",\n    wrapLabelNWords = 9,\n  } = config ?? {};\n  // Initialize Mermaid graph configuration\n  let mermaidGraph = withStyles\n    ? `%%{init: {'flowchart': {'curve': '${curveStyle}'}}}%%\\ngraph TD;\\n`\n    : \"graph TD;\\n\";\n  if (withStyles) {\n    // Node formatting templates\n    const defaultClassLabel = \"default\";\n    const formatDict: Record<string, string> = {\n      [defaultClassLabel]: \"{0}({1})\",\n    };\n    if (firstNode !== undefined) {\n      formatDict[firstNode] = \"{0}([{1}]):::first\";\n    }\n    if (lastNode !== undefined) {\n      formatDict[lastNode] = \"{0}([{1}]):::last\";\n    }\n\n    // Add nodes to the graph\n    for (const [key, node] of Object.entries(nodes)) {\n      const nodeName = node.name.split(\":\").pop() ?? \"\";\n      const label = MARKDOWN_SPECIAL_CHARS.some(\n        (char) => nodeName.startsWith(char) && nodeName.endsWith(char)\n      )\n        ? `<p>${nodeName}</p>`\n        : nodeName;\n\n      let finalLabel = label;\n      if (Object.keys(node.metadata ?? {}).length) {\n        finalLabel += `<hr/><small><em>${Object.entries(node.metadata ?? {})\n          .map(([k, v]) => `${k} = ${v}`)\n          .join(\"\\n\")}</em></small>`;\n      }\n\n      const nodeLabel = (formatDict[key] ?? formatDict[defaultClassLabel])\n        .replace(\"{0}\", _escapeNodeLabel(key))\n        .replace(\"{1}\", finalLabel);\n\n      mermaidGraph += `\\t${nodeLabel}\\n`;\n    }\n  }\n\n  // Group edges by their common prefixes\n  const edgeGroups: Record<string, Edge[]> = {};\n  for (const edge of edges) {\n    const srcParts = edge.source.split(\":\");\n    const tgtParts = edge.target.split(\":\");\n    const commonPrefix = srcParts\n      .filter((src, i) => src === tgtParts[i])\n      .join(\":\");\n    if (!edgeGroups[commonPrefix]) {\n      edgeGroups[commonPrefix] = [];\n    }\n    edgeGroups[commonPrefix].push(edge);\n  }\n\n  const seenSubgraphs = new Set<string>();\n\n  // sort prefixes by path length for correct nesting\n  function sortPrefixesByDepth(prefixes: string[]): string[] {\n    return [...prefixes].sort((a, b) => {\n      return a.split(\":\").length - b.split(\":\").length;\n    });\n  }\n\n  function addSubgraph(edges: Edge[], prefix: string): void {\n    const selfLoop = edges.length === 1 && edges[0].source === edges[0].target;\n    if (prefix && !selfLoop) {\n      const subgraph = prefix.split(\":\").pop()!;\n\n      if (seenSubgraphs.has(prefix)) {\n        throw new Error(\n          `Found duplicate subgraph '${subgraph}' at '${prefix} -- this likely means that ` +\n            \"you're reusing a subgraph node with the same name. \" +\n            \"Please adjust your graph to have subgraph nodes with unique names.\"\n        );\n      }\n\n      seenSubgraphs.add(prefix);\n      mermaidGraph += `\\tsubgraph ${subgraph}\\n`;\n    }\n\n    // all nested prefixes for this level, sorted by depth\n    const nestedPrefixes = sortPrefixesByDepth(\n      Object.keys(edgeGroups).filter(\n        (nestedPrefix) =>\n          nestedPrefix.startsWith(`${prefix}:`) &&\n          nestedPrefix !== prefix &&\n          nestedPrefix.split(\":\").length === prefix.split(\":\").length + 1\n      )\n    );\n\n    for (const nestedPrefix of nestedPrefixes) {\n      addSubgraph(edgeGroups[nestedPrefix], nestedPrefix);\n    }\n\n    for (const edge of edges) {\n      const { source, target, data, conditional } = edge;\n\n      let edgeLabel = \"\";\n      if (data !== undefined) {\n        let edgeData = data;\n        const words = edgeData.split(\" \");\n        if (words.length > wrapLabelNWords) {\n          edgeData = Array.from(\n            { length: Math.ceil(words.length / wrapLabelNWords) },\n            (_, i) =>\n              words\n                .slice(i * wrapLabelNWords, (i + 1) * wrapLabelNWords)\n                .join(\" \")\n          ).join(\"&nbsp;<br>&nbsp;\");\n        }\n        edgeLabel = conditional\n          ? ` -. &nbsp;${edgeData}&nbsp; .-> `\n          : ` -- &nbsp;${edgeData}&nbsp; --> `;\n      } else {\n        edgeLabel = conditional ? \" -.-> \" : \" --> \";\n      }\n\n      mermaidGraph += `\\t${_escapeNodeLabel(\n        source\n      )}${edgeLabel}${_escapeNodeLabel(target)};\\n`;\n    }\n\n    if (prefix && !selfLoop) {\n      mermaidGraph += \"\\tend\\n\";\n    }\n  }\n\n  // Start with the top-level edges (no common prefix)\n  addSubgraph(edgeGroups[\"\"] ?? [], \"\");\n\n  // Add remaining top-level subgraphs\n  for (const prefix in edgeGroups) {\n    if (!prefix.includes(\":\") && prefix !== \"\") {\n      addSubgraph(edgeGroups[prefix], prefix);\n    }\n  }\n\n  // Add custom styles for nodes\n  if (withStyles) {\n    mermaidGraph += _generateMermaidGraphStyles(nodeColors ?? {});\n  }\n\n  return mermaidGraph;\n}\n\n/**\n * Renders Mermaid graph using the Mermaid.INK API.\n *\n * @example\n * ```javascript\n * const image = await drawMermaidImage(mermaidSyntax, {\n *   backgroundColor: \"white\",\n *   imageType: \"png\",\n * });\n * fs.writeFileSync(\"image.png\", image);\n * ```\n *\n * @param mermaidSyntax - The Mermaid syntax to render.\n * @param config - The configuration for the image.\n * @returns The image as a Blob.\n */\nexport async function drawMermaidImage(\n  mermaidSyntax: string,\n  config?: {\n    /**\n     * The type of image to render.\n     * @default \"png\"\n     */\n    imageType?: \"png\" | \"jpeg\" | \"webp\";\n    backgroundColor?: string;\n  }\n) {\n  let backgroundColor = config?.backgroundColor ?? \"white\";\n  const imageType = config?.imageType ?? \"png\";\n\n  const mermaidSyntaxEncoded = toBase64Url(mermaidSyntax);\n\n  // Check if the background color is a hexadecimal color code using regex\n  if (backgroundColor !== undefined) {\n    const hexColorPattern = /^#(?:[0-9a-fA-F]{3}){1,2}$/;\n    if (!hexColorPattern.test(backgroundColor)) {\n      backgroundColor = `!${backgroundColor}`;\n    }\n  }\n  const imageUrl = `https://mermaid.ink/img/${mermaidSyntaxEncoded}?bgColor=${backgroundColor}&type=${imageType}`;\n  const res = await fetch(imageUrl);\n  if (!res.ok) {\n    throw new Error(\n      [\n        `Failed to render the graph using the Mermaid.INK API.`,\n        `Status code: ${res.status}`,\n        `Status text: ${res.statusText}`,\n      ].join(\"\\n\")\n    );\n  }\n  const content = await res.blob();\n  return content;\n}\n"],"mappings":"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"}