Home / Function/ SchemaGraph() — supabase Function Reference

SchemaGraph() — supabase Function Reference

Architecture documentation for the SchemaGraph() function in SchemaGraph.tsx from the supabase codebase.

Entity Profile

Dependency Diagram

graph TD
  2f23e199_5215_d9cd_0558_237d0dc2652f["SchemaGraph()"]
  6afd133c_f4fa_d02d_b814_82224fed4bf4["getLayoutedElementsViaDagre()"]
  2f23e199_5215_d9cd_0558_237d0dc2652f -->|calls| 6afd133c_f4fa_d02d_b814_82224fed4bf4
  c873669a_f88c_bb16_2076_ffa1fc0fc1a7["getGraphDataFromTables()"]
  2f23e199_5215_d9cd_0558_237d0dc2652f -->|calls| c873669a_f88c_bb16_2076_ffa1fc0fc1a7
  style 2f23e199_5215_d9cd_0558_237d0dc2652f fill:#6366f1,stroke:#818cf8,color:#fff

Relationship Graph

Source Code

apps/studio/components/interfaces/Database/Schemas/SchemaGraph.tsx lines 38–361

export const SchemaGraph = () => {
  const { ref } = useParams()
  const { resolvedTheme } = useTheme()
  const { data: project } = useSelectedProjectQuery()
  const { selectedSchema, setSelectedSchema } = useQuerySchemaState()

  const [copied, setCopied] = useState(false)
  useEffect(() => {
    if (copied) {
      setTimeout(() => setCopied(false), 2000)
    }
  }, [copied])

  const [isDownloading, setIsDownloading] = useState(false)

  const miniMapNodeColor = '#111318'
  const miniMapMaskColor = resolvedTheme?.includes('dark')
    ? 'rgb(17, 19, 24, .8)'
    : 'rgb(237, 237, 237, .8)'

  const reactFlowInstance = useReactFlow()
  const nodeTypes = useMemo(
    () => ({
      table: TableNode,
    }),
    []
  )

  const {
    data: schemas,
    error: errorSchemas,
    isSuccess: isSuccessSchemas,
    isPending: isLoadingSchemas,
    isError: isErrorSchemas,
  } = useSchemasQuery({
    projectRef: project?.ref,
    connectionString: project?.connectionString,
  })

  const {
    data: tables,
    error: errorTables,
    isSuccess: isSuccessTables,
    isPending: isLoadingTables,
    isError: isErrorTables,
  } = useTablesQuery({
    projectRef: project?.ref,
    connectionString: project?.connectionString,
    schema: selectedSchema,
    includeColumns: true,
  })

  const schema = (schemas ?? []).find((s) => s.name === selectedSchema)
  const [_, setStoredPositions] = useLocalStorage(
    LOCAL_STORAGE_KEYS.SCHEMA_VISUALIZER_POSITIONS(ref as string, schema?.id ?? 0),
    {}
  )

  const { can: canUpdateTables } = useAsyncCheckPermissions(
    PermissionAction.TENANT_SQL_ADMIN_WRITE,
    'tables'
  )

  const { isSchemaLocked } = useIsProtectedSchema({ schema: selectedSchema })

  const canAddTables = canUpdateTables && !isSchemaLocked

  const resetLayout = () => {
    const nodes = reactFlowInstance.getNodes()
    const edges = reactFlowInstance.getEdges()

    getLayoutedElementsViaDagre(nodes, edges)
    reactFlowInstance.setNodes(nodes)
    reactFlowInstance.setEdges(edges)
    setTimeout(() => reactFlowInstance.fitView({}))
    saveNodePositions()
  }

  const saveNodePositions = () => {
    if (schema === undefined) return console.error('Schema is required')

    const nodes = reactFlowInstance.getNodes()
    if (nodes.length > 0) {
      const nodesPositionData = nodes.reduce((a, b) => {
        return { ...a, [b.id]: b.position }
      }, {})
      setStoredPositions(nodesPositionData)
    }
  }

  const downloadImage = (format: 'png' | 'svg') => {
    const reactflowViewport = document.querySelector('.react-flow__viewport') as HTMLElement
    if (!reactflowViewport) return

    setIsDownloading(true)
    const width = reactflowViewport.clientWidth
    const height = reactflowViewport.clientHeight
    const { x, y, zoom } = reactFlowInstance.getViewport()

    if (format === 'svg') {
      toSvg(reactflowViewport, {
        backgroundColor: 'white',
        width,
        height,
        style: {
          width: width.toString(),
          height: height.toString(),
          transform: `translate(${x}px, ${y}px) scale(${zoom})`,
        },
      })
        .then((data) => {
          const a = document.createElement('a')
          a.setAttribute('download', `supabase-schema-${ref}.svg`)
          a.setAttribute('href', data)
          a.click()
          toast.success('Successfully downloaded as SVG')
        })
        .catch((error) => {
          console.error('Failed to download:', error)
          toast.error('Failed to download current view:', error.message)
        })
        .finally(() => {
          setIsDownloading(false)
        })
    } else if (format === 'png') {
      toPng(reactflowViewport, {
        backgroundColor: 'white',
        width,
        height,
        style: {
          width: width.toString(),
          height: height.toString(),
          transform: `translate(${x}px, ${y}px) scale(${zoom})`,
        },
      })
        .then((data) => {
          const a = document.createElement('a')
          a.setAttribute('download', `supabase-schema-${ref}.png`)
          a.setAttribute('href', data)
          a.click()
          toast.success('Successfully downloaded as PNG')
        })
        .catch((error) => {
          console.error('Failed to download:', error)
          toast.error('Failed to download current view:', error.message)
        })
        .finally(() => {
          setIsDownloading(false)
        })
    }
  }

  useEffect(() => {
    if (isSuccessTables && isSuccessSchemas && tables.length > 0) {
      const schema = schemas.find((s) => s.name === selectedSchema) as PostgresSchema
      getGraphDataFromTables(ref as string, schema, tables).then(({ nodes, edges }) => {
        reactFlowInstance.setNodes(nodes)
        reactFlowInstance.setEdges(edges)
        setTimeout(() => reactFlowInstance.fitView({})) // it needs to happen during next event tick
      })
    }
  }, [isSuccessTables, isSuccessSchemas, tables, resolvedTheme])

  return (
    <>
      <div className="flex items-center justify-between p-4 border-b border-muted">
        {isLoadingSchemas && (
          <div className="h-[34px] w-[260px] bg-foreground-lighter rounded shimmering-loader" />
        )}

        {isErrorSchemas && (
          <AlertError error={errorSchemas as any} subject="Failed to retrieve schemas" />
        )}

        {isSuccessSchemas && (
          <>
            <SchemaSelector
              className="w-[180px]"
              size="tiny"
              showError={false}
              selectedSchemaName={selectedSchema}
              onSelectSchema={setSelectedSchema}
            />
            <div className="flex items-center gap-x-2">
              <ButtonTooltip
                type="outline"
                icon={copied ? <Check /> : <Copy />}
                onClick={() => {
                  if (tables) {
                    copyToClipboard(tablesToSQL(tables))
                    setCopied(true)
                  }
                }}
                tooltip={{
                  content: {
                    side: 'bottom',
                    text: (
                      <div className="max-w-[180px] space-y-2 text-foreground-light">
                        <p className="text-foreground">Note</p>
                        <p>
                          This schema is for context or debugging only. Table order and constraints
                          may be invalid. Not meant to be run as-is.
                        </p>
                      </div>
                    ),
                  },
                }}
              >
                Copy as SQL
              </ButtonTooltip>
              <DropdownMenu>
                <DropdownMenuTrigger asChild>
                  <ButtonTooltip
                    aria-label="Download Schema"
                    type="default"
                    loading={isDownloading}
                    className="px-1.5"
                    icon={<Download />}
                    tooltip={{ content: { side: 'bottom', text: 'Download current view' } }}
                  />
                </DropdownMenuTrigger>
                <DropdownMenuContent className="w-32">
                  <DropdownMenuItem onClick={() => downloadImage('png')}>
                    Download as PNG
                  </DropdownMenuItem>
                  <DropdownMenuItem onClick={() => downloadImage('svg')}>
                    Download as SVG
                  </DropdownMenuItem>
                </DropdownMenuContent>
              </DropdownMenu>
              <ButtonTooltip
                type="default"
                onClick={resetLayout}
                tooltip={{
                  content: {
                    side: 'bottom',
                    text: 'Automatically arrange the layout of all nodes',
                  },
                }}
              >
                Auto layout
              </ButtonTooltip>
            </div>
          </>
        )}
      </div>
      {isLoadingTables && (
        <div className="w-full h-full flex items-center justify-center gap-x-2">
          <Loader2 className="animate-spin text-foreground-light" size={16} />
          <p className="text-sm text-foreground-light">Loading tables</p>
        </div>
      )}
      {isErrorTables && (
        <div className="w-full h-full flex items-center justify-center px-20">
          <AlertError subject="Failed to retrieve tables" error={errorTables} />
        </div>
      )}
      {isSuccessTables && (
        <>
          {tables.length === 0 ? (
            <div className="flex items-center justify-center w-full h-full">
              <Admonition
                type="default"
                className="max-w-md"
                title="No tables in schema"
                description={
                  isSchemaLocked
                    ? `The “${selectedSchema}” schema is managed by Supabase and is read-only through
                    the dashboard.`
                    : !canUpdateTables
                      ? 'You need additional permissions to create tables'
                      : `The “${selectedSchema}” schema doesn’t have any tables.`
                }
              >
                {canAddTables && (
                  <Button asChild className="mt-2" type="default" icon={<Plus />}>
                    <Link href={`/project/${ref}/editor?create=table`}>New table</Link>
                  </Button>
                )}
              </Admonition>
            </div>
          ) : (
            <div className="w-full h-full">
              <ReactFlow
                defaultNodes={[]}
                defaultEdges={[]}
                defaultEdgeOptions={{
                  type: 'smoothstep',
                  animated: true,
                  deletable: false,
                  style: {
                    stroke: 'hsl(var(--border-stronger))',
                    strokeWidth: 1,
                  },
                }}
                nodeTypes={nodeTypes}
                fitView
                minZoom={0.8}
                maxZoom={1.8}
                proOptions={{ hideAttribution: true }}
                onNodeDragStop={() => saveNodePositions()}
              >
                <Background
                  gap={16}
                  className="[&>*]:stroke-foreground-muted opacity-[25%]"
                  variant={BackgroundVariant.Dots}
                  color={'inherit'}
                />
                <MiniMap
                  pannable
                  zoomable
                  nodeColor={miniMapNodeColor}
                  maskColor={miniMapMaskColor}
                  className="border rounded-md shadow-sm"
                />
                <SchemaGraphLegend />
              </ReactFlow>
            </div>
          )}
        </>
      )}
    </>
  )
}

Subdomains

Frequently Asked Questions

What does SchemaGraph() do?
SchemaGraph() is a function in the supabase codebase.
What does SchemaGraph() call?
SchemaGraph() calls 2 function(s): getGraphDataFromTables, getLayoutedElementsViaDagre.

Analyze Your Own Codebase

Get architecture documentation, dependency graphs, and domain analysis for your codebase in minutes.

Try Supermodel Free