Liam ERD Documentation

repository·main·Indexed 26 days ago

https://github.com/liam-hq/liam

A tool for automatically generating interactive Entity-Relationship (ER) diagrams from database schemas. Documentation covers the LangGraph.js-based agentic workflow for database design, including specialized subgraphs for requirements analysis (pmAgent), schema design (dbAgent), and validation (qaAgent), as well as the Splinter database linter and E2E testing with Playwright.

Tokens
77.9K
Snippets
124
Records
693
Agent score
90%

What's inside Liam ERD

  1. Overview of @liam-hq/mcp-server

    main

    The @liam-hq/mcp-server package implements the Model Context Protocol (MCP) to integrate Liam development tools with AI agents. It specifically enables Cursor IDE to interact with Liam's UI components through the following tools:

    • list_components: Lists all UI components available in the Liam UI package.
    • get_component_files: Retrieves the contents of files belonging to a specific UI component.
  2. LangGraph.js Documentation Structure and Topics

    main

    The LangGraph.js documentation is organized into three main areas to help you build controllable agents:

    Core Concepts

    • Graph state definition, basic construction, and node patterns: Learn how to define the state and build the basic graph structure.
    • Control flow: Implement branching, map-reduce, conditional routing, and loop control.
    • State management: Design state schemas, handle updates, and implement persistence patterns.

    Advanced Features

    • Streaming: Implement various stream modes, including token-level and custom data streaming.
    • Tool calling: Use ToolNode and manage state updates resulting from tool execution.
    • Multi-agent systems: Implement agent communication, supervisory patterns, and multi-turn conversations.

    Production & Platform

    • Advanced features: Implement subgraphs, retries, caching, runtime configuration, and structured output.
  3. Understand the LangGraph Chat Workflow Architecture

    main

    The @liam-hq/agent package implements a LangGraph-based workflow for processing chat messages. The workflow consists of several specialized nodes and subgraphs that manage the lifecycle of database design and requirements analysis:

    • validateInitialSchema: Validates user-provided schemas using a PostgreSQL deparser and PGLite.
    • leadAgent: An intelligent router (subgraph) that classifies requests and directs them to specialized agents.
    • pmAgent: A subgraph for requirements analysis and artifact management.
    • dbAgent: A subgraph for database schema design and migration execution.
    • qaAgent: A subgraph for testing and validation (includes testcase generation and schema validation).

    The workflow uses conditional routing, type-safe state transitions, and a standardized retry policy (max 3 attempts) for all nodes.

  4. Pan and Zoom the diagram interface

    main

    You can navigate the database schema diagram using panning and zooming controls to focus on specific areas or view the entire structure.

    Panning (Moving the view):

    • Press the Space key and drag the mouse to move the view.

    Zooming:

    • Use Ctrl + scroll up to zoom in, or scroll down to zoom out.
    • Use pinch gestures on a trackpad.
    • Use the + and - buttons located in the toolbar.
  5. Setup the Schema-Bench workspace

    main

    To begin benchmarking, you must clean any existing workspace and initialize a new one containing multiple datasets (e.g., default, entity-extraction, ambiguous-recall, relational-inference, and logical-deletion).

    Run the following command to create the benchmark-workspace/ directory and populate it with datasets:

    rm -rf benchmark-workspace && pnpm --filter @liam-hq/schema-bench setupWorkspace
  6. Manage database migrations with Supabase

    main

    The project uses Supabase Branching for migration management. Migrations are stored in frontend/internal-packages/db/supabase/migrations and run sequentially by timestamp.

    Key Constraints:

    • Migrations must be backward compatible with the previous app version (@liam-hq/app).
    • The app must be able to function with both the old and new database schemas during deployment transitions.
    • There is no guaranteed order between app deployments and migrations; migrations may complete before or after the app deployment.
  7. Stream custom events using `dispatchCustomEvent`

    main

    To emit custom events that can be captured via the streamEvents API, use the dispatchCustomEvent function within your nodes. You can then listen for the on_custom_event event type in the event stream.

    import { dispatchCustomEvent } from "@langchain/core/callbacks/dispatch";
    
    // Inside a node
    const myNodeWithEvents = async (state: typeof MessagesAnnotation.State, config: RunnableConfig) => {
      await dispatchCustomEvent("my_custom_event", {
        message: "hello from node 1",
      }, config);
      return { messages: state.messages };
    };
    
    // Consuming the stream
    const eventStream = await graphWithEvents.streamEvents(inputs, { version: "v2" });
    for await (const { event, data } of eventStream) {
      if (event === "on_custom_event") {
        console.log(data);
      }
    }
  8. Manage conversation history in LangGraph

    main

    To prevent models from being overwhelmed by long histories, you can implement a message filtering strategy within your agent nodes. Use an Annotation.Root to define your state and a reducer to manage how new messages are appended. Inside your node function, slice the message array to keep only the necessary context (e.g., the most recent messages) before invoking the model.

    import { Annotation, END, START, StateGraph } from "@langchain/langgraph";
    import { BaseMessage, HumanMessage, AIMessage } from "@langchain/core/messages";
    
    // Define state
    const AgentState = Annotation.Root({
      messages: Annotation<BaseMessage[]>({
        reducer: (x, y) => x.concat(y),
      }),
    });
    
    // Filter messages to keep only the most recent
    const filterMessages = (messages: BaseMessage[]) => {
      return messages.slice(-1); // Keep only the last message
    };
    
    // Agent node that uses filtered messages
    const agent = async (state: typeof AgentState.State) => {
      const { messages } = state;
      const filteredMessages = filterMessages(messages);
      
      const response = await model.invoke(filteredMessages);
      
      return {
        messages: [response],
      };
    };
    
    // Build workflow
    const workflow = new StateGraph(AgentState)
      .addNode("agent", agent)
      .addEdge(START, "agent")
      .addEdge("agent", END);
    
    const app = workflow.compile();
  9. Define tools for LangGraph using ToolNode

    main

    To use tools within a LangGraph workflow, define them using the tool function from @langchain/core/tools. You can then wrap these tools in a ToolNode from @langchain/langgraph/prebuilt. ToolNode is a LangChain Runnable that accepts graph state (containing a list of messages) and returns state updates with the results of the tool calls. It requires the graph state to have a messages key with an appropriate reducer (e.g., using MessagesAnnotation).

    import { tool } from "@langchain/core/tools";
    import * as v from "valibot";
    import { toJsonSchema } from "@valibot/to-json-schema";
    
    const getWeather = tool((input) => {
      if (["sf", "san francisco"].includes(input.location.toLowerCase())) {
        return "It's 60 degrees and foggy.";
      } else {
        return "It's 90 degrees and sunny.";
      }
    }, {
      name: "get_weather",
      description: "Call to get the current weather.",
      schema: toJsonSchema(v.object({
        location: v.pipe(v.string(), v.description("Location to get the weather for.")),
      })),
    });
  10. Stream custom data using `streamMode: 'custom'`

    main

    You can stream custom data from within a node by using the config.writer function provided in the node's configuration. To consume this data, use .stream() with streamMode: 'custom'.

    // Inside a node
    const myOtherNode = async (state: typeof MessagesAnnotation.State, config: RunnableConfig) => {
      config.writer?.({
        myCustomData: "some_value",
        moreCustomData: "some_other_value",
      });
      return { messages: state.messages };
    };
    
    // Consuming the stream
    const inputs = { messages: [new HumanMessage("hello world")] };
    for await (const chunk of await graph.stream(inputs, {
      streamMode: "custom",
    })) {
      console.log(chunk);
    }