Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: add metadata to hive ui with linked directive support #6517

Open
wants to merge 13 commits into
base: main
Choose a base branch
from

Conversation

jdolle
Copy link
Collaborator

@jdolle jdolle commented Feb 13, 2025

Background

We need a way to provide and visualize metadata for subgraphs.

#2747
#2818

Description

This adds metadata to fields via a directive.
In this PR, I added a way to view metadata on fields via the explorer. But to close these issues, we will need additional views and behaviors. E.g. filtering the explorer based on metadata, and viewing metadata on a field's insights page.
Screenshot 2025-02-13 at 12 54 30 PM

Checklist

  • Testing

@jdolle jdolle self-assigned this Feb 13, 2025

This comment was marked as off-topic.

Copy link
Contributor

github-actions bot commented Feb 13, 2025

📚 Storybook Deployment

The latest changes are available as preview in: https://5f992a99.hive-storybook.pages.dev

Copy link
Contributor

github-actions bot commented Feb 13, 2025

🐋 This PR was built and pushed to the following Docker images:

Targets: build

Platforms: linux/arm64

Image Tag: 6c5c165e29e8253a09fc7b1691a32cd5e8d7c441

Docker Bake metadata
{
"app": {
  "buildx.build.provenance": {
    "buildType": "https://mobyproject.org/buildkit@v1",
    "materials": [
      {
        "uri": "pkg:docker/[email protected]?platform=linux%2Farm64",
        "digest": {
          "sha256": "f5a0871ab03b035c58bdb3007c3d177b001c2145c18e81817b71624dcf7d8bff"
        }
      }
    ],
    "invocation": {
      "configSource": {
        "entryPoint": "services.dockerfile"
      },
      "parameters": {
        "frontend": "dockerfile.v0",
        "args": {
          "build-arg:HEALTHCHECK_CMD": "wget --spider -q http://127.0.0.1:${PORT}/api/health",
          "build-arg:IMAGE_DESCRIPTION": "The app of the GraphQL Hive project.",
          "build-arg:IMAGE_TITLE": "graphql-hive/app",
          "build-arg:PORT": "3000",
          "build-arg:RELEASE": "6c5c165e29e8253a09fc7b1691a32cd5e8d7c441",
          "build-arg:SERVICE_DIR_NAME": "@hive/app",
          "context:dist": "local:dist",
          "context:shared": "local:shared",
          "frontend.caps": "moby.buildkit.frontend.contexts+forward",
          "local-sessionid:context": "ob3vzrhz5faabymfqxsubkhg3",
          "local-sessionid:dockerfile": "ob3vzrhz5faabymfqxsubkhg3",
          "local-sessionid:shared": "ob3vzrhz5faabymfqxsubkhg3"
        },
        "locals": [
          {
            "name": "context"
          },
          {
            "name": "dist"
          },
          {
            "name": "dockerfile"
          },
          {
            "name": "shared"
          }
        ]
      },
      "environment": {
        "platform": "linux/arm64"
      }
    }
  },
  "buildx.build.ref": "builder-ed970045-9563-45fe-826e-916713cd23dd/builder-ed970045-9563-45fe-826e-916713cd23dd0/oobuvk7syeijo67zyyhytlkkk",
  "containerimage.config.digest": "sha256:ba862f1d1aa683f1867e27cf6af19036caa29bd9c7e75315f32b594c0e850907",
  "containerimage.descriptor": {
    "mediaType": "application/vnd.docker.distribution.manifest.v2+json",
    "digest": "sha256:a2b4eebf56145a3dadd621b241fd5f8c4dc480298af201c26534947b75781a57",
    "size": 2075,
    "platform": {
      "architecture": "arm64",
      "os": "linux"
    }
  },
  "containerimage.digest": "sha256:a2b4eebf56145a3dadd621b241fd5f8c4dc480298af201c26534947b75781a57",
  "image.name": "ghcr.io/graphql-hive/app:6c5c165e29e8253a09fc7b1691a32cd5e8d7c441-arm64,ghcr.io/graphql-hive/app:hive_metadata-arm64"
},
"buildx.build.warnings": [
  {
    "vertex": "sha256:94f61d6ac2d67e23827b0f629f7955fa2d16bcccf076f3044bf746b76e24fed1",
    "level": 1,
    "short": "VW5kZWZpbmVkVmFyOiBVc2FnZSBvZiB1bmRlZmluZWQgdmFyaWFibGUgJyRJTUFHRV9USVRMRScgKGxpbmUgMTIp",
    "detail": [
      "VmFyaWFibGVzIHNob3VsZCBiZSBkZWZpbmVkIGJlZm9yZSB0aGVpciB1c2U="
    ],
    "url": "https://docs.docker.com/go/dockerfile/rule/undefined-var/",
    "sourceInfo": {
      "filename": "services.dockerfile",
      "data": "RlJPTSBub2RlOjIyLjEzLjAtc2xpbQoKUlVOIGFwdC1nZXQgdXBkYXRlICYmIGFwdC1nZXQgaW5zdGFsbCAteSB3Z2V0IGNhLWNlcnRpZmljYXRlcyAmJiBybSAtcmYgL3Zhci9saWIvYXB0L2xpc3RzLyoKCkFSRyBTRVJWSUNFX0RJUl9OQU1FCldPUktESVIgL3Vzci9zcmMvYXBwLyRTRVJWSUNFX0RJUl9OQU1FCgpDT1BZIC0tZnJvbT1kaXN0IC4gL3Vzci9zcmMvYXBwLyRTRVJWSUNFX0RJUl9OQU1FLwpDT1BZIC0tZnJvbT1zaGFyZWQgLiAvCgpMQUJFTCBvcmcub3BlbmNvbnRhaW5lcnMuaW1hZ2UubGljZW5zZXM9TUlUCkxBQkVMIG9yZy5vcGVuY29udGFpbmVycy5pbWFnZS50aXRsZT0kSU1BR0VfVElUTEUKTEFCRUwgb3JnLm9wZW5jb250YWluZXJzLmltYWdlLnZlcnNpb249JFJFTEVBU0UKTEFCRUwgb3JnLm9wZW5jb250YWluZXJzLmltYWdlLmRlc2NyaXB0aW9uPSRJTUFHRV9ERVNDUklQVElPTgpMQUJFTCBvcmcub3BlbmNvbnRhaW5lcnMuaW1hZ2UuYXV0aG9ycz0iVGhlIEd1aWxkIgpMQUJFTCBvcmcub3BlbmNvbnRhaW5lcnMuaW1hZ2UudmVuZG9yPSJLYW1pbCBLaXNpZWxhIgpMQUJFTCBvcmcub3BlbmNvbnRhaW5lcnMuaW1hZ2UudXJsPSJodHRwczovL2dpdGh1Yi5jb20vZ3JhcGhxbC1oaXZlL3BsYXRmb3JtIgpMQUJFTCBvcmcub3BlbmNvbnRhaW5lcnMuaW1hZ2Uuc291cmNlPSJodHRwczovL2dpdGh1Yi5jb20vZ3JhcGhxbC1oaXZlL3BsYXRmb3JtIgoKRU5WIEVOVklST05NRU5UIHByb2R1Y3Rpb24KRU5WIFJFTEVBU0UgJFJFTEVBU0UKRU5WIFBPUlQgJFBPUlQKCkhFQUxUSENIRUNLIC0taW50ZXJ2YWw9NXMgXAogIC0tdGltZW91dD01cyBcCiAgLS1zdGFydC1wZXJpb2Q9NXMgXAogIC0tcmV0cmllcz02IFwKICBDTUQgJEhFQUxUSENIRUNLX0NNRAoKRU5UUllQT0lOVCBbICIvZW50cnlwb2ludC5zaCIgXQo=",
      "language": "Dockerfile"
    },
    "range": [
      {
        "start": {
          "line": 12
        },
        "end": {
          "line": 12
        }
      }
    ]
  },
  {
    "vertex": "sha256:94f61d6ac2d67e23827b0f629f7955fa2d16bcccf076f3044bf746b76e24fed1",
    "level": 1,
    "short": "VW5kZWZpbmVkVmFyOiBVc2FnZSBvZiB1bmRlZmluZWQgdmFyaWFibGUgJyRJTUFHRV9ERVNDUklQVElPTicgKGxpbmUgMTQp",
    "detail": [
      "VmFyaWFibGVzIHNob3VsZCBiZSBkZWZpbmVkIGJlZm9yZSB0aGVpciB1c2U="
    ],
    "url": "https://docs.docker.com/go/dockerfile/rule/undefined-var/",
    "sourceInfo": {
      "filename": "services.dockerfile",
      "data": "RlJPTSBub2RlOjIyLjEzLjAtc2xpbQoKUlVOIGFwdC1nZXQgdXBkYXRlICYmIGFwdC1nZXQgaW5zdGFsbCAteSB3Z2V0IGNhLWNlcnRpZmljYXRlcyAmJiBybSAtcmYgL3Zhci9saWIvYXB0L2xpc3RzLyoKCkFSRyBTRVJWSUNFX0RJUl9OQU1FCldPUktESVIgL3Vzci9zcmMvYXBwLyRTRVJWSUNFX0RJUl9OQU1FCgpDT1BZIC0tZnJvbT1kaXN0IC4gL3Vzci9zcmMvYXBwLyRTRVJWSUNFX0RJUl9OQU1FLwpDT1BZIC0tZnJvbT1zaGFyZWQgLiAvCgpMQUJFTCBvcmcub3BlbmNvbnRhaW5lcnMuaW1hZ2UubGljZW5zZXM9TUlUCkxBQkVMIG9yZy5vcGVuY29udGFpbmVycy5pbWFnZS50aXRsZT0kSU1BR0VfVElUTEUKTEFCRUwgb3JnLm9wZW5jb250YWluZXJzLmltYWdlLnZlcnNpb249JFJFTEVBU0UKTEFCRUwgb3JnLm9wZW5jb250YWluZXJzLmltYWdlLmRlc2NyaXB0aW9uPSRJTUFHRV9ERVNDUklQVElPTgpMQUJFTCBvcmcub3BlbmNvbnRhaW5lcnMuaW1hZ2UuYXV0aG9ycz0iVGhlIEd1aWxkIgpMQUJFTCBvcmcub3BlbmNvbnRhaW5lcnMuaW1hZ2UudmVuZG9yPSJLYW1pbCBLaXNpZWxhIgpMQUJFTCBvcmcub3BlbmNvbnRhaW5lcnMuaW1hZ2UudXJsPSJodHRwczovL2dpdGh1Yi5jb20vZ3JhcGhxbC1oaXZlL3BsYXRmb3JtIgpMQUJFTCBvcmcub3BlbmNvbnRhaW5lcnMuaW1hZ2Uuc291cmNlPSJodHRwczovL2dpdGh1Yi5jb20vZ3JhcGhxbC1oaXZlL3BsYXRmb3JtIgoKRU5WIEVOVklST05NRU5UIHByb2R1Y3Rpb24KRU5WIFJFTEVBU0UgJFJFTEVBU0UKRU5WIFBPUlQgJFBPUlQKCkhFQUxUSENIRUNLIC0taW50ZXJ2YWw9NXMgXAogIC0tdGltZW91dD01cyBcCiAgLS1zdGFydC1wZXJpb2Q9NXMgXAogIC0tcmV0cmllcz02IFwKICBDTUQgJEhFQUxUSENIRUNLX0NNRAoKRU5UUllQT0lOVCBbICIvZW50cnlwb2ludC5zaCIgXQo=",
      "language": "Dockerfile"
    },
    "range": [
      {
        "start": {
          "line": 14
        },
        "end": {
          "line": 14
        }
      }
    ]
  },
  {
    "vertex": "sha256:94f61d6ac2d67e23827b0f629f7955fa2d16bcccf076f3044bf746b76e24fed1",
    "level": 1,
    "short": "TGVnYWN5S2V5VmFsdWVGb3JtYXQ6ICJFTlYga2V5PXZhbHVlIiBzaG91bGQgYmUgdXNlZCBpbnN0ZWFkIG9mIGxlZ2FjeSAiRU5WIGtleSB2YWx1ZSIgZm9ybWF0IChsaW5lIDIwKQ==",
    "detail": [
      "TGVnYWN5IGtleS92YWx1ZSBmb3JtYXQgd2l0aCB3aGl0ZXNwYWNlIHNlcGFyYXRvciBzaG91bGQgbm90IGJlIHVzZWQ="
    ],
    "url": "https://docs.docker.com/go/dockerfile/rule/legacy-key-value-format/",
    "sourceInfo": {
      "filename": "services.dockerfile",
      "data": "RlJPTSBub2RlOjIyLjEzLjAtc2xpbQoKUlVOIGFwdC1nZXQgdXBkYXRlICYmIGFwdC1nZXQgaW5zdGFsbCAteSB3Z2V0IGNhLWNlcnRpZmljYXRlcyAmJiBybSAtcmYgL3Zhci9saWIvYXB0L2xpc3RzLyoKCkFSRyBTRVJWSUNFX0RJUl9OQU1FCldPUktESVIgL3Vzci9zcmMvYXBwLyRTRVJWSUNFX0RJUl9OQU1FCgpDT1BZIC0tZnJvbT1kaXN0IC4gL3Vzci9zcmMvYXBwLyRTRVJWSUNFX0RJUl9OQU1FLwpDT1BZIC0tZnJvbT1zaGFyZWQgLiAvCgpMQUJFTCBvcmcub3BlbmNvbnRhaW5lcnMuaW1hZ2UubGljZW5zZXM9TUlUCkxBQkVMIG9yZy5vcGVuY29udGFpbmVycy5pbWFnZS50aXRsZT0kSU1BR0VfVElUTEUKTEFCRUwgb3JnLm9wZW5jb250YWluZXJzLmltYWdlLnZlcnNpb249JFJFTEVBU0UKTEFCRUwgb3JnLm9wZW5jb250YWluZXJzLmltYWdlLmRlc2NyaXB0aW9uPSRJTUFHRV9ERVNDUklQVElPTgpMQUJFTCBvcmcub3BlbmNvbnRhaW5lcnMuaW1hZ2UuYXV0aG9ycz0iVGhlIEd1aWxkIgpMQUJFTCBvcmcub3BlbmNvbnRhaW5lcnMuaW1hZ2UudmVuZG9yPSJLYW1pbCBLaXNpZWxhIgpMQUJFTCBvcmcub3BlbmNvbnRhaW5lcnMuaW1hZ2UudXJsPSJodHRwczovL2dpdGh1Yi5jb20vZ3JhcGhxbC1oaXZlL3BsYXRmb3JtIgpMQUJFTCBvcmcub3BlbmNvbnRhaW5lcnMuaW1hZ2Uuc291cmNlPSJodHRwczovL2dpdGh1Yi5jb20vZ3JhcGhxbC1oaXZlL3BsYXRmb3JtIgoKRU5WIEVOVklST05NRU5UIHByb2R1Y3Rpb24KRU5WIFJFTEVBU0UgJFJFTEVBU0UKRU5WIFBPUlQgJFBPUlQKCkhFQUxUSENIRUNLIC0taW50ZXJ2YWw9NXMgXAogIC0tdGltZW91dD01cyBcCiAgLS1zdGFydC1wZXJpb2Q9NXMgXAogIC0tcmV0cmllcz02IFwKICBDTUQgJEhFQUxUSENIRUNLX0NNRAoKRU5UUllQT0lOVCBbICIvZW50cnlwb2ludC5zaCIgXQo=",
      "language": "Dockerfile"
    },
    "range": [
      {
        "start": {
          "line": 20
        },
        "end": {
          "line": 20
        }
      }
    ]
  },
  {
    "vertex": "sha256:94f61d6ac2d67e23827b0f629f7955fa2d16bcccf076f3044bf746b76e24fed1",
    "level": 1,
    "short": "VW5kZWZpbmVkVmFyOiBVc2FnZSBvZiB1bmRlZmluZWQgdmFyaWFibGUgJyRQT1JUJyAobGluZSAyMik=",
    "detail": [
      "VmFyaWFibGVzIHNob3VsZCBiZSBkZWZpbmVkIGJlZm9yZSB0aGVpciB1c2U="
    ],
    "url": "https://docs.docker.com/go/dockerfile/rule/undefined-var/",
    "sourceInfo": {
      "filename": "services.dockerfile",
      "data": "RlJPTSBub2RlOjIyLjEzLjAtc2xpbQoKUlVOIGFwdC1nZXQgdXBkYXRlICYmIGFwdC1nZXQgaW5zdGFsbCAteSB3Z2V0IGNhLWNlcnRpZmljYXRlcyAmJiBybSAtcmYgL3Zhci9saWIvYXB0L2xpc3RzLyoKCkFSRyBTRVJWSUNFX0RJUl9OQU1FCldPUktESVIgL3Vzci9zcmMvYXBwLyRTRVJWSUNFX0RJUl9OQU1FCgpDT1BZIC0tZnJvbT1kaXN0IC4gL3Vzci9zcmMvYXBwLyRTRVJWSUNFX0RJUl9OQU1FLwpDT1BZIC0tZnJvbT1zaGFyZWQgLiAvCgpMQUJFTCBvcmcub3BlbmNvbnRhaW5lcnMuaW1hZ2UubGljZW5zZXM9TUlUCkxBQkVMIG9yZy5vcGVuY29udGFpbmVycy5pbWFnZS50aXRsZT0kSU1BR0VfVElUTEUKTEFCRUwgb3JnLm9wZW5jb250YWluZXJzLmltYWdlLnZlcnNpb249JFJFTEVBU0UKTEFCRUwgb3JnLm9wZW5jb250YWluZXJzLmltYWdlLmRlc2NyaXB0aW9uPSRJTUFHRV9ERVNDUklQVElPTgpMQUJFTCBvcmcub3BlbmNvbnRhaW5lcnMuaW1hZ2UuYXV0aG9ycz0iVGhlIEd1aWxkIgpMQUJFTCBvcmcub3BlbmNvbnRhaW5lcnMuaW1hZ2UudmVuZG9yPSJLYW1pbCBLaXNpZWxhIgpMQUJFTCBvcmcub3BlbmNvbnRhaW5lcnMuaW1hZ2UudXJsPSJodHRwczovL2dpdGh1Yi5jb20vZ3JhcGhxbC1oaXZlL3BsYXRmb3JtIgpMQUJFTCBvcmcub3BlbmNvbnRhaW5lcnMuaW1hZ2Uuc291cmNlPSJodHRwczovL2dpdGh1Yi5jb20vZ3JhcGhxbC1oaXZlL3BsYXRmb3JtIgoKRU5WIEVOVklST05NRU5UIHByb2R1Y3Rpb24KRU5WIFJFTEVBU0UgJFJFTEVBU0UKRU5WIFBPUlQgJFBPUlQKCkhFQUxUSENIRUNLIC0taW50ZXJ2YWw9NXMgXAogIC0tdGltZW91dD01cyBcCiAgLS1zdGFydC1wZXJpb2Q9NXMgXAogIC0tcmV0cmllcz02IFwKICBDTUQgJEhFQUxUSENIRUNLX0NNRAoKRU5UUllQT0lOVCBbICIvZW50cnlwb2ludC5zaCIgXQo=",
      "language": "Dockerfile"
    },
    "range": [
      {
        "start": {
          "line": 22
        },
        "end": {
          "line": 22
        }
      }
    ]
  },
  {
    "vertex": "sha256:f566d20768d5de07570a6c8a6f8ff75579add219b415f5f6622ba0876b86001f",
    "level": 1,
    "short": "TGVnYWN5S2V5VmFsdWVGb3JtYXQ6ICJFTlYga2V5PXZhbHVlIiBzaG91bGQgYmUgdXNlZCBpbnN0ZWFkIG9mIGxlZ2FjeSAiRU5WIGtleSB2YWx1ZSIgZm9ybWF0IChsaW5lIDEwKQ==",
    "detail": [
      "TGVnYWN5IGtleS92YWx1ZSBmb3JtYXQgd2l0aCB3aGl0ZXNwYWNlIHNlcGFyYXRvciBzaG91bGQgbm90IGJlIHVzZWQ="
    ],
    "url": "https://docs.docker.com/go/dockerfile/rule/legacy-key-value-format/",
    "sourceInfo": {
      "filename": "migrations.dockerfile",
      "data": "RlJPTSBub2RlOjIyLjEzLjAtc2xpbQoKUlVOIGFwdC1nZXQgdXBkYXRlICYmIGFwdC1nZXQgaW5zdGFsbCAteSBjYS1jZXJ0aWZpY2F0ZXMKCldPUktESVIgL3Vzci9zcmMvYXBwCgpDT1BZIC0tZnJvbT1kaXN0IC4gL3Vzci9zcmMvYXBwLwpDT1BZIC0tZnJvbT1zaGFyZWQgLiAvCgpFTlYgRU5WSVJPTk1FTlQgcHJvZHVjdGlvbgpFTlYgTk9ERV9FTlYgcHJvZHVjdGlvbgpFTlYgUkVMRUFTRSAkUkVMRUFTRQoKTEFCRUwgb3JnLm9wZW5jb250YWluZXJzLmltYWdlLmxpY2Vuc2VzPU1JVApMQUJFTCBvcmcub3BlbmNvbnRhaW5lcnMuaW1hZ2UudGl0bGU9JElNQUdFX1RJVExFCkxBQkVMIG9yZy5vcGVuY29udGFpbmVycy5pbWFnZS52ZXJzaW9uPSRSRUxFQVNFCkxBQkVMIG9yZy5vcGVuY29udGFpbmVycy5pbWFnZS5kZXNjcmlwdGlvbj0kSU1BR0VfREVTQ1JJUFRJT04KTEFCRUwgb3JnLm9wZW5jb250YWluZXJzLmltYWdlLmF1dGhvcnM9IlRoZSBHdWlsZCIKTEFCRUwgb3JnLm9wZW5jb250YWluZXJzLmltYWdlLnZlbmRvcj0iS2FtaWwgS2lzaWVsYSIKTEFCRUwgb3JnLm9wZW5jb250YWluZXJzLmltYWdlLnVybD0iaHR0cHM6Ly9naXRodWIuY29tL2dyYXBocWwtaGl2ZS9wbGF0Zm9ybSIKTEFCRUwgb3JnLm9wZW5jb250YWluZXJzLmltYWdlLnNvdXJjZT0iaHR0cHM6Ly9naXRodWIuY29tL2dyYXBocWwtaGl2ZS9wbGF0Zm9ybSIKCkVOVFJZUE9JTlQgWyAiL2VudHJ5cG9pbnQuc2giIF0K",
      "language": "Dockerfile"
    },
    "range": [
      {
        "start": {
          "line": 10
        },
        "end": {
          "line": 10
        }
      }
    ]
  },
  {
    "vertex": "sha256:f566d20768d5de07570a6c8a6f8ff75579add219b415f5f6622ba0876b86001f",
    "level": 1,
    "short": "TGVnYWN5S2V5VmFsdWVGb3JtYXQ6ICJFTlYga2V5PXZhbHVlIiBzaG91bGQgYmUgdXNlZCBpbnN0ZWFkIG9mIGxlZ2FjeSAiRU5WIGtleSB2YWx1ZSIgZm9ybWF0IChsaW5lIDExKQ==",
    "detail": [
      "TGVnYWN5IGtleS92YWx1ZSBmb3JtYXQgd2l0aCB3aGl0ZXNwYWNlIHNlcGFyYXRvciBzaG91bGQgbm90IGJlIHVzZWQ="
    ],
    "url": "https://docs.docker.com/go/dockerfile/rule/legacy-key-value-format/",
    "sourceInfo": {
      "filename": "migrations.dockerfile",
      "data": "RlJPTSBub2RlOjIyLjEzLjAtc2xpbQoKUlVOIGFwdC1nZXQgdXBkYXRlICYmIGFwdC1nZXQgaW5zdGFsbCAteSBjYS1jZXJ0aWZpY2F0ZXMKCldPUktESVIgL3Vzci9zcmMvYXBwCgpDT1BZIC0tZnJvbT1kaXN0IC4gL3Vzci9zcmMvYXBwLwpDT1BZIC0tZnJvbT1zaGFyZWQgLiAvCgpFTlYgRU5WSVJPTk1FTlQgcHJvZHVjdGlvbgpFTlYgTk9ERV9FTlYgcHJvZHVjdGlvbgpFTlYgUkVMRUFTRSAkUkVMRUFTRQoKTEFCRUwgb3JnLm9wZW5jb250YWluZXJzLmltYWdlLmxpY2Vuc2VzPU1JVApMQUJFTCBvcmcub3BlbmNvbnRhaW5lcnMuaW1hZ2UudGl0bGU9JElNQUdFX1RJVExFCkxBQkVMIG9yZy5vcGVuY29udGFpbmVycy5pbWFnZS52ZXJzaW9uPSRSRUxFQVNFCkxBQkVMIG9yZy5vcGVuY29udGFpbmVycy5pbWFnZS5kZXNjcmlwdGlvbj0kSU1BR0VfREVTQ1JJUFRJT04KTEFCRUwgb3JnLm9wZW5jb250YWluZXJzLmltYWdlLmF1dGhvcnM9IlRoZSBHdWlsZCIKTEFCRUwgb3JnLm9wZW5jb250YWluZXJzLmltYWdlLnZlbmRvcj0iS2FtaWwgS2lzaWVsYSIKTEFCRUwgb3JnLm9wZW5jb250YWluZXJzLmltYWdlLnVybD0iaHR0cHM6Ly9naXRodWIuY29tL2dyYXBocWwtaGl2ZS9wbGF0Zm9ybSIKTEFCRUwgb3JnLm9wZW5jb250YWluZXJzLmltYWdlLnNvdXJjZT0iaHR0cHM6Ly9naXRodWIuY29tL2dyYXBocWwtaGl2ZS9wbGF0Zm9ybSIKCkVOVFJZUE9JTlQgWyAiL2VudHJ5cG9pbnQuc2giIF0K",
      "language": "Dockerfile"
    },
    "range": [
      {
        "start": {
          "line": 11
        },
        "end": {
          "line": 11
        }
      }
    ]
  },
  {
    "vertex": "sha256:f566d20768d5de07570a6c8a6f8ff75579add219b415f5f6622ba0876b86001f",
    "level": 1,
    "short": "TGVnYWN5S2V5VmFsdWVGb3JtYXQ6ICJFTlYga2V5PXZhbHVlIiBzaG91bGQgYmUgdXNlZCBpbnN0ZWFkIG9mIGxlZ2FjeSAiRU5WIGtleSB2YWx1ZSIgZm9ybWF0IChsaW5lIDEyKQ==",
    "detail": [
      "TGVnYWN5IGtleS92YWx1ZSBmb3JtYXQgd2l0aCB3aGl0ZXNwYWNlIHNlcGFyYXRvciBzaG91bGQgbm90IGJlIHVzZWQ="
    ],
    "url": "https://docs.docker.com/go/dockerfile/rule/legacy-key-value-format/",
    "sourceInfo": {
      "filename": "migrations.dockerfile",
      "data": "RlJPTSBub2RlOjIyLjEzLjAtc2xpbQoKUlVOIGFwdC1nZXQgdXBkYXRlICYmIGFwdC1nZXQgaW5zdGFsbCAteSBjYS1jZXJ0aWZpY2F0ZXMKCldPUktESVIgL3Vzci9zcmMvYXBwCgpDT1BZIC0tZnJvbT1kaXN0IC4gL3Vzci9zcmMvYXBwLwpDT1BZIC0tZnJvbT1zaGFyZWQgLiAvCgpFTlYgRU5WSVJPTk1FTlQgcHJvZHVjdGlvbgpFTlYgTk9ERV9FTlYgcHJvZHVjdGlvbgpFTlYgUkVMRUFTRSAkUkVMRUFTRQoKTEFCRUwgb3JnLm9wZW5jb250YWluZXJzLmltYWdlLmxpY2Vuc2VzPU1JVApMQUJFTCBvcmcub3BlbmNvbnRhaW5lcnMuaW1hZ2UudGl0bGU9JElNQUdFX1RJVExFCkxBQkVMIG9yZy5vcGVuY29udGFpbmVycy5pbWFnZS52ZXJzaW9uPSRSRUxFQVNFCkxBQkVMIG9yZy5vcGVuY29udGFpbmVycy5pbWFnZS5kZXNjcmlwdGlvbj0kSU1BR0VfREVTQ1JJUFRJT04KTEFCRUwgb3JnLm9wZW5jb250YWluZXJzLmltYWdlLmF1dGhvcnM9IlRoZSBHdWlsZCIKTEFCRUwgb3JnLm9wZW5jb250YWluZXJzLmltYWdlLnZlbmRvcj0iS2FtaWwgS2lzaWVsYSIKTEFCRUwgb3JnLm9wZW5jb250YWluZXJzLmltYWdlLnVybD0iaHR0cHM6Ly9naXRodWIuY29tL2dyYXBocWwtaGl2ZS9wbGF0Zm9ybSIKTEFCRUwgb3JnLm9wZW5jb250YWluZXJzLmltYWdlLnNvdXJjZT0iaHR0cHM6Ly9naXRodWIuY29tL2dyYXBocWwtaGl2ZS9wbGF0Zm9ybSIKCkVOVFJZUE9JTlQgWyAiL2VudHJ5cG9pbnQuc2giIF0K",
      "language": "Dockerfile"
    },
    "range": [
      {
        "start": {
          "line": 12
        },
        "end": {
          "line": 12
        }
      }
    ]
  },
  {
    "vertex": "sha256:94f61d6ac2d67e23827b0f629f7955fa2d16bcccf076f3044bf746b76e24fed1",
    "level": 1,
    "short": "VW5kZWZpbmVkVmFyOiBVc2FnZSBvZiB1bmRlZmluZWQgdmFyaWFibGUgJyRSRUxFQVNFJyAobGluZSAxMyk=",
    "detail": [
      "VmFyaWFibGVzIHNob3VsZCBiZSBkZWZpbmVkIGJlZm9yZSB0aGVpciB1c2U="
    ],
    "url": "https://docs.docker.com/go/dockerfile/rule/undefined-var/",
    "sourceInfo": {
      "filename": "services.dockerfile",
      "data": "RlJPTSBub2RlOjIyLjEzLjAtc2xpbQoKUlVOIGFwdC1nZXQgdXBkYXRlICYmIGFwdC1nZXQgaW5zdGFsbCAteSB3Z2V0IGNhLWNlcnRpZmljYXRlcyAmJiBybSAtcmYgL3Zhci9saWIvYXB0L2xpc3RzLyoKCkFSRyBTRVJWSUNFX0RJUl9OQU1FCldPUktESVIgL3Vzci9zcmMvYXBwLyRTRVJWSUNFX0RJUl9OQU1FCgpDT1BZIC0tZnJvbT1kaXN0IC4gL3Vzci9zcmMvYXBwLyRTRVJWSUNFX0RJUl9OQU1FLwpDT1BZIC0tZnJvbT1zaGFyZWQgLiAvCgpMQUJFTCBvcmcub3BlbmNvbnRhaW5lcnMuaW1hZ2UubGljZW5zZXM9TUlUCkxBQkVMIG9yZy5vcGVuY29udGFpbmVycy5pbWFnZS50aXRsZT0kSU1BR0VfVElUTEUKTEFCRUwgb3JnLm9wZW5jb250YWluZXJzLmltYWdlLnZlcnNpb249JFJFTEVBU0UKTEFCRUwgb3JnLm9wZW5jb250YWluZXJzLmltYWdlLmRlc2NyaXB0aW9uPSRJTUFHRV9ERVNDUklQVElPTgpMQUJFTCBvcmcub3BlbmNvbnRhaW5lcnMuaW1hZ2UuYXV0aG9ycz0iVGhlIEd1aWxkIgpMQUJFTCBvcmcub3BlbmNvbnRhaW5lcnMuaW1hZ2UudmVuZG9yPSJLYW1pbCBLaXNpZWxhIgpMQUJFTCBvcmcub3BlbmNvbnRhaW5lcnMuaW1hZ2UudXJsPSJodHRwczovL2dpdGh1Yi5jb20vZ3JhcGhxbC1oaXZlL3BsYXRmb3JtIgpMQUJFTCBvcmcub3BlbmNvbnRhaW5lcnMuaW1hZ2Uuc291cmNlPSJodHRwczovL2dpdGh1Yi5jb20vZ3JhcGhxbC1oaXZlL3BsYXRmb3JtIgoKRU5WIEVOVklST05NRU5UIHByb2R1Y3Rpb24KRU5WIFJFTEVBU0UgJFJFTEVBU0UKRU5WIFBPUlQgJFBPUlQKCkhFQUxUSENIRUNLIC0taW50ZXJ2YWw9NXMgXAogIC0tdGltZW91dD01cyBcCiAgLS1zdGFydC1wZXJpb2Q9NXMgXAogIC0tcmV0cmllcz02IFwKICBDTUQgJEhFQUxUSENIRUNLX0NNRAoKRU5UUllQT0lOVCBbICIvZW50cnlwb2ludC5zaCIgXQo=",
      "language": "Dockerfile"
    },
    "range": [
      {
        "start": {
          "line": 13
        },
        "end": {
          "line": 13
        }
      }
    ]
  },
  {
    "vertex": "sha256:94f61d6ac2d67e23827b0f629f7955fa2d16bcccf076f3044bf746b76e24fed1",
    "level": 1,
    "short": "VW5kZWZpbmVkVmFyOiBVc2FnZSBvZiB1bmRlZmluZWQgdmFyaWFibGUgJyRSRUxFQVNFJyAobGluZSAyMSk=",
    "detail": [
      "VmFyaWFibGVzIHNob3VsZCBiZSBkZWZpbmVkIGJlZm9yZSB0aGVpciB1c2U="
    ],
    "url": "https://docs.docker.com/go/dockerfile/rule/undefined-var/",
    "sourceInfo": {
      "filename": "services.dockerfile",
      "data": "RlJPTSBub2RlOjIyLjEzLjAtc2xpbQoKUlVOIGFwdC1nZXQgdXBkYXRlICYmIGFwdC1nZXQgaW5zdGFsbCAteSB3Z2V0IGNhLWNlcnRpZmljYXRlcyAmJiBybSAtcmYgL3Zhci9saWIvYXB0L2xpc3RzLyoKCkFSRyBTRVJWSUNFX0RJUl9OQU1FCldPUktESVIgL3Vzci9zcmMvYXBwLyRTRVJWSUNFX0RJUl9OQU1FCgpDT1BZIC0tZnJvbT1kaXN0IC4gL3Vzci9zcmMvYXBwLyRTRVJWSUNFX0RJUl9OQU1FLwpDT1BZIC0tZnJvbT1zaGFyZWQgLiAvCgpMQUJFTCBvcmcub3BlbmNvbnRhaW5lcnMuaW1hZ2UubGljZW5zZXM9TUlUCkxBQkVMIG9yZy5vcGVuY29udGFpbmVycy5pbWFnZS50aXRsZT0kSU1BR0VfVElUTEUKTEFCRUwgb3JnLm9wZW5jb250YWluZXJzLmltYWdlLnZlcnNpb249JFJFTEVBU0UKTEFCRUwgb3JnLm9wZW5jb250YWluZXJzLmltYWdlLmRlc2NyaXB0aW9uPSRJTUFHRV9ERVNDUklQVElPTgpMQUJFTCBvcmcub3BlbmNvbnRhaW5lcnMuaW1hZ2UuYXV0aG9ycz0iVGhlIEd1aWxkIgpMQUJFTCBvcmcub3BlbmNvbnRhaW5lcnMuaW1hZ2UudmVuZG9yPSJLYW1pbCBLaXNpZWxhIgpMQUJFTCBvcmcub3BlbmNvbnRhaW5lcnMuaW1hZ2UudXJsPSJodHRwczovL2dpdGh1Yi5jb20vZ3JhcGhxbC1oaXZlL3BsYXRmb3JtIgpMQUJFTCBvcmcub3BlbmNvbnRhaW5lcnMuaW1hZ2Uuc291cmNlPSJodHRwczovL2dpdGh1Yi5jb20vZ3JhcGhxbC1oaXZlL3BsYXRmb3JtIgoKRU5WIEVOVklST05NRU5UIHByb2R1Y3Rpb24KRU5WIFJFTEVBU0UgJFJFTEVBU0UKRU5WIFBPUlQgJFBPUlQKCkhFQUxUSENIRUNLIC0taW50ZXJ2YWw9NXMgXAogIC0tdGltZW91dD01cyBcCiAgLS1zdGFydC1wZXJpb2Q9NXMgXAogIC0tcmV0cmllcz02IFwKICBDTUQgJEhFQUxUSENIRUNLX0NNRAoKRU5UUllQT0lOVCBbICIvZW50cnlwb2ludC5zaCIgXQo=",
      "language": "Dockerfile"
    },
    "range": [
      {
        "start": {
          "line": 21
        },
        "end": {
          "line": 21
        }
      }
    ]
  },
  {
    "vertex": "sha256:94f61d6ac2d67e23827b0f629f7955fa2d16bcccf076f3044bf746b76e24fed1",
    "level": 1,
    "short": "TGVnYWN5S2V5VmFsdWVGb3JtYXQ6ICJFTlYga2V5PXZhbHVlIiBzaG91bGQgYmUgdXNlZCBpbnN0ZWFkIG9mIGxlZ2FjeSAiRU5WIGtleSB2YWx1ZSIgZm9ybWF0IChsaW5lIDIxKQ==",
    "detail": [
      "TGVnYWN5IGtleS92YWx1ZSBmb3JtYXQgd2l0aCB3aGl0ZXNwYWNlIHNlcGFyYXRvciBzaG91bGQgbm90IGJlIHVzZWQ="
    ],
    "url": "https://docs.docker.com/go/dockerfile/rule/legacy-key-value-format/",
    "sourceInfo": {
      "filename": "services.dockerfile",
      "data": "RlJPTSBub2RlOjIyLjEzLjAtc2xpbQoKUlVOIGFwdC1nZXQgdXBkYXRlICYmIGFwdC1nZXQgaW5zdGFsbCAteSB3Z2V0IGNhLWNlcnRpZmljYXRlcyAmJiBybSAtcmYgL3Zhci9saWIvYXB0L2xpc3RzLyoKCkFSRyBTRVJWSUNFX0RJUl9OQU1FCldPUktESVIgL3Vzci9zcmMvYXBwLyRTRVJWSUNFX0RJUl9OQU1FCgpDT1BZIC0tZnJvbT1kaXN0IC4gL3Vzci9zcmMvYXBwLyRTRVJWSUNFX0RJUl9OQU1FLwpDT1BZIC0tZnJvbT1zaGFyZWQgLiAvCgpMQUJFTCBvcmcub3BlbmNvbnRhaW5lcnMuaW1hZ2UubGljZW5zZXM9TUlUCkxBQkVMIG9yZy5vcGVuY29udGFpbmVycy5pbWFnZS50aXRsZT0kSU1BR0VfVElUTEUKTEFCRUwgb3JnLm9wZW5jb250YWluZXJzLmltYWdlLnZlcnNpb249JFJFTEVBU0UKTEFCRUwgb3JnLm9wZW5jb250YWluZXJzLmltYWdlLmRlc2NyaXB0aW9uPSRJTUFHRV9ERVNDUklQVElPTgpMQUJFTCBvcmcub3BlbmNvbnRhaW5lcnMuaW1hZ2UuYXV0aG9ycz0iVGhlIEd1aWxkIgpMQUJFTCBvcmcub3BlbmNvbnRhaW5lcnMuaW1hZ2UudmVuZG9yPSJLYW1pbCBLaXNpZWxhIgpMQUJFTCBvcmcub3BlbmNvbnRhaW5lcnMuaW1hZ2UudXJsPSJodHRwczovL2dpdGh1Yi5jb20vZ3JhcGhxbC1oaXZlL3BsYXRmb3JtIgpMQUJFTCBvcmcub3BlbmNvbnRhaW5lcnMuaW1hZ2Uuc291cmNlPSJodHRwczovL2dpdGh1Yi5jb20vZ3JhcGhxbC1oaXZlL3BsYXRmb3JtIgoKRU5WIEVOVklST05NRU5UIHByb2R1Y3Rpb24KRU5WIFJFTEVBU0UgJFJFTEVBU0UKRU5WIFBPUlQgJFBPUlQKCkhFQUxUSENIRUNLIC0taW50ZXJ2YWw9NXMgXAogIC0tdGltZW91dD01cyBcCiAgLS1zdGFydC1wZXJpb2Q9NXMgXAogIC0tcmV0cmllcz02IFwKICBDTUQgJEhFQUxUSENIRUNLX0NNRAoKRU5UUllQT0lOVCBbICIvZW50cnlwb2ludC5zaCIgXQo=",
      "language": "Dockerfile"
    },
    "range": [
      {
        "start": {
          "line": 21
        },
        "end": {
          "line": 21
        }
      }
    ]
  },
  {
    "vertex": "sha256:94f61d6ac2d67e23827b0f629f7955fa2d16bcccf076f3044bf746b76e24fed1",
    "level": 1,
    "short": "TGVnYWN5S2V5VmFsdWVGb3JtYXQ6ICJFTlYga2V5PXZhbHVlIiBzaG91bGQgYmUgdXNlZCBpbnN0ZWFkIG9mIGxlZ2FjeSAiRU5WIGtleSB2YWx1ZSIgZm9ybWF0IChsaW5lIDIyKQ==",
    "detail": [
      "TGVnYWN5IGtleS92YWx1ZSBmb3JtYXQgd2l0aCB3aGl0ZXNwYWNlIHNlcGFyYXRvciBzaG91bGQgbm90IGJlIHVzZWQ="
    ],
    "url": "https://docs.docker.com/go/dockerfile/rule/legacy-key-value-format/",
    "sourceInfo": {
      "filename": "services.dockerfile",
      "data": "RlJPTSBub2RlOjIyLjEzLjAtc2xpbQoKUlVOIGFwdC1nZXQgdXBkYXRlICYmIGFwdC1nZXQgaW5zdGFsbCAteSB3Z2V0IGNhLWNlcnRpZmljYXRlcyAmJiBybSAtcmYgL3Zhci9saWIvYXB0L2xpc3RzLyoKCkFSRyBTRVJWSUNFX0RJUl9OQU1FCldPUktESVIgL3Vzci9zcmMvYXBwLyRTRVJWSUNFX0RJUl9OQU1FCgpDT1BZIC0tZnJvbT1kaXN0IC4gL3Vzci9zcmMvYXBwLyRTRVJWSUNFX0RJUl9OQU1FLwpDT1BZIC0tZnJvbT1zaGFyZWQgLiAvCgpMQUJFTCBvcmcub3BlbmNvbnRhaW5lcnMuaW1hZ2UubGljZW5zZXM9TUlUCkxBQkVMIG9yZy5vcGVuY29udGFpbmVycy5pbWFnZS50aXRsZT0kSU1BR0VfVElUTEUKTEFCRUwgb3JnLm9wZW5jb250YWluZXJzLmltYWdlLnZlcnNpb249JFJFTEVBU0UKTEFCRUwgb3JnLm9wZW5jb250YWluZXJzLmltYWdlLmRlc2NyaXB0aW9uPSRJTUFHRV9ERVNDUklQVElPTgpMQUJFTCBvcmcub3BlbmNvbnRhaW5lcnMuaW1hZ2UuYXV0aG9ycz0iVGhlIEd1aWxkIgpMQUJFTCBvcmcub3BlbmNvbnRhaW5lcnMuaW1hZ2UudmVuZG9yPSJLYW1pbCBLaXNpZWxhIgpMQUJFTCBvcmcub3BlbmNvbnRhaW5lcnMuaW1hZ2UudXJsPSJodHRwczovL2dpdGh1Yi5jb20vZ3JhcGhxbC1oaXZlL3BsYXRmb3JtIgpMQUJFTCBvcmcub3BlbmNvbnRhaW5lcnMuaW1hZ2Uuc291cmNlPSJodHRwczovL2dpdGh1Yi5jb20vZ3JhcGhxbC1oaXZlL3BsYXRmb3JtIgoKRU5WIEVOVklST05NRU5UIHByb2R1Y3Rpb24KRU5WIFJFTEVBU0UgJFJFTEVBU0UKRU5WIFBPUlQgJFBPUlQKCkhFQUxUSENIRUNLIC0taW50ZXJ2YWw9NXMgXAogIC0tdGltZW91dD01cyBcCiAgLS1zdGFydC1wZXJpb2Q9NXMgXAogIC0tcmV0cmllcz02IFwKICBDTUQgJEhFQUxUSENIRUNLX0NNRAoKRU5UUllQT0lOVCBbICIvZW50cnlwb2ludC5zaCIgXQo=",
      "language": "Dockerfile"
    },
    "range": [
      {
        "start": {
          "line": 22
        },
        "end": {
          "line": 22
        }
      }
    ]
  },
  {
    "vertex": "sha256:f566d20768d5de07570a6c8a6f8ff75579add219b415f5f6622ba0876b86001f",
    "level": 1,
    "short": "VW5kZWZpbmVkVmFyOiBVc2FnZSBvZiB1bmRlZmluZWQgdmFyaWFibGUgJyRSRUxFQVNFJyAobGluZSAxMik=",
    "detail": [
      "VmFyaWFibGVzIHNob3VsZCBiZSBkZWZpbmVkIGJlZm9yZSB0aGVpciB1c2U="
    ],
    "url": "https://docs.docker.com/go/dockerfile/rule/undefined-var/",
    "sourceInfo": {
      "filename": "migrations.dockerfile",
      "data": "RlJPTSBub2RlOjIyLjEzLjAtc2xpbQoKUlVOIGFwdC1nZXQgdXBkYXRlICYmIGFwdC1nZXQgaW5zdGFsbCAteSBjYS1jZXJ0aWZpY2F0ZXMKCldPUktESVIgL3Vzci9zcmMvYXBwCgpDT1BZIC0tZnJvbT1kaXN0IC4gL3Vzci9zcmMvYXBwLwpDT1BZIC0tZnJvbT1zaGFyZWQgLiAvCgpFTlYgRU5WSVJPTk1FTlQgcHJvZHVjdGlvbgpFTlYgTk9ERV9FTlYgcHJvZHVjdGlvbgpFTlYgUkVMRUFTRSAkUkVMRUFTRQoKTEFCRUwgb3JnLm9wZW5jb250YWluZXJzLmltYWdlLmxpY2Vuc2VzPU1JVApMQUJFTCBvcmcub3BlbmNvbnRhaW5lcnMuaW1hZ2UudGl0bGU9JElNQUdFX1RJVExFCkxBQkVMIG9yZy5vcGVuY29udGFpbmVycy5pbWFnZS52ZXJzaW9uPSRSRUxFQVNFCkxBQkVMIG9yZy5vcGVuY29udGFpbmVycy5pbWFnZS5kZXNjcmlwdGlvbj0kSU1BR0VfREVTQ1JJUFRJT04KTEFCRUwgb3JnLm9wZW5jb250YWluZXJzLmltYWdlLmF1dGhvcnM9IlRoZSBHdWlsZCIKTEFCRUwgb3JnLm9wZW5jb250YWluZXJzLmltYWdlLnZlbmRvcj0iS2FtaWwgS2lzaWVsYSIKTEFCRUwgb3JnLm9wZW5jb250YWluZXJzLmltYWdlLnVybD0iaHR0cHM6Ly9naXRodWIuY29tL2dyYXBocWwtaGl2ZS9wbGF0Zm9ybSIKTEFCRUwgb3JnLm9wZW5jb250YWluZXJzLmltYWdlLnNvdXJjZT0iaHR0cHM6Ly9naXRodWIuY29tL2dyYXBocWwtaGl2ZS9wbGF0Zm9ybSIKCkVOVFJZUE9JTlQgWyAiL2VudHJ5cG9pbnQuc2giIF0K",
      "language": "Dockerfile"
    },
    "range": [
      {
        "start": {
          "line": 12
        },
        "end": {
          "line": 12
        }
      }
    ]
  },
  {
    "vertex": "sha256:f566d20768d5de07570a6c8a6f8ff75579add219b415f5f6622ba0876b86001f",
    "level": 1,
    "short": "VW5kZWZpbmVkVmFyOiBVc2FnZSBvZiB1bmRlZmluZWQgdmFyaWFibGUgJyRJTUFHRV9USVRMRScgKGxpbmUgMTUp",
    "detail": [
      "VmFyaWFibGVzIHNob3VsZCBiZSBkZWZpbmVkIGJlZm9yZSB0aGVpciB1c2U="
    ],
    "url": "https://docs.docker.com/go/dockerfile/rule/undefined-var/",
    "sourceInfo": {
      "filename": "migrations.dockerfile",
      "data": "RlJPTSBub2RlOjIyLjEzLjAtc2xpbQoKUlVOIGFwdC1nZXQgdXBkYXRlICYmIGFwdC1nZXQgaW5zdGFsbCAteSBjYS1jZXJ0aWZpY2F0ZXMKCldPUktESVIgL3Vzci9zcmMvYXBwCgpDT1BZIC0tZnJvbT1kaXN0IC4gL3Vzci9zcmMvYXBwLwpDT1BZIC0tZnJvbT1zaGFyZWQgLiAvCgpFTlYgRU5WSVJPTk1FTlQgcHJvZHVjdGlvbgpFTlYgTk9ERV9FTlYgcHJvZHVjdGlvbgpFTlYgUkVMRUFTRSAkUkVMRUFTRQoKTEFCRUwgb3JnLm9wZW5jb250YWluZXJzLmltYWdlLmxpY2Vuc2VzPU1JVApMQUJFTCBvcmcub3BlbmNvbnRhaW5lcnMuaW1hZ2UudGl0bGU9JElNQUdFX1RJVExFCkxBQkVMIG9yZy5vcGVuY29udGFpbmVycy5pbWFnZS52ZXJzaW9uPSRSRUxFQVNFCkxBQkVMIG9yZy5vcGVuY29udGFpbmVycy5pbWFnZS5kZXNjcmlwdGlvbj0kSU1BR0VfREVTQ1JJUFRJT04KTEFCRUwgb3JnLm9wZW5jb250YWluZXJzLmltYWdlLmF1dGhvcnM9IlRoZSBHdWlsZCIKTEFCRUwgb3JnLm9wZW5jb250YWluZXJzLmltYWdlLnZlbmRvcj0iS2FtaWwgS2lzaWVsYSIKTEFCRUwgb3JnLm9wZW5jb250YWluZXJzLmltYWdlLnVybD0iaHR0cHM6Ly9naXRodWIuY29tL2dyYXBocWwtaGl2ZS9wbGF0Zm9ybSIKTEFCRUwgb3JnLm9wZW5jb250YWluZXJzLmltYWdlLnNvdXJjZT0iaHR0cHM6Ly9naXRodWIuY29tL2dyYXBocWwtaGl2ZS9wbGF0Zm9ybSIKCkVOVFJZUE9JTlQgWyAiL2VudHJ5cG9pbnQuc2giIF0K",
      "language": "Dockerfile"
    },
    "range": [
      {
        "start": {
          "line": 15
        },
        "end": {
          "line": 15
        }
      }
    ]
  },
  {
    "vertex": "sha256:f566d20768d5de07570a6c8a6f8ff75579add219b415f5f6622ba0876b86001f",
    "level": 1,
    "short": "VW5kZWZpbmVkVmFyOiBVc2FnZSBvZiB1bmRlZmluZWQgdmFyaWFibGUgJyRJTUFHRV9ERVNDUklQVElPTicgKGxpbmUgMTcp",
    "detail": [
      "VmFyaWFibGVzIHNob3VsZCBiZSBkZWZpbmVkIGJlZm9yZSB0aGVpciB1c2U="
    ],
    "url": "https://docs.docker.com/go/dockerfile/rule/undefined-var/",
    "sourceInfo": {
      "filename": "migrations.dockerfile",
      "data": "RlJPTSBub2RlOjIyLjEzLjAtc2xpbQoKUlVOIGFwdC1nZXQgdXBkYXRlICYmIGFwdC1nZXQgaW5zdGFsbCAteSBjYS1jZXJ0aWZpY2F0ZXMKCldPUktESVIgL3Vzci9zcmMvYXBwCgpDT1BZIC0tZnJvbT1kaXN0IC4gL3Vzci9zcmMvYXBwLwpDT1BZIC0tZnJvbT1zaGFyZWQgLiAvCgpFTlYgRU5WSVJPTk1FTlQgcHJvZHVjdGlvbgpFTlYgTk9ERV9FTlYgcHJvZHVjdGlvbgpFTlYgUkVMRUFTRSAkUkVMRUFTRQoKTEFCRUwgb3JnLm9wZW5jb250YWluZXJzLmltYWdlLmxpY2Vuc2VzPU1JVApMQUJFTCBvcmcub3BlbmNvbnRhaW5lcnMuaW1hZ2UudGl0bGU9JElNQUdFX1RJVExFCkxBQkVMIG9yZy5vcGVuY29udGFpbmVycy5pbWFnZS52ZXJzaW9uPSRSRUxFQVNFCkxBQkVMIG9yZy5vcGVuY29udGFpbmVycy5pbWFnZS5kZXNjcmlwdGlvbj0kSU1BR0VfREVTQ1JJUFRJT04KTEFCRUwgb3JnLm9wZW5jb250YWluZXJzLmltYWdlLmF1dGhvcnM9IlRoZSBHdWlsZCIKTEFCRUwgb3JnLm9wZW5jb250YWluZXJzLmltYWdlLnZlbmRvcj0iS2FtaWwgS2lzaWVsYSIKTEFCRUwgb3JnLm9wZW5jb250YWluZXJzLmltYWdlLnVybD0iaHR0cHM6Ly9naXRodWIuY29tL2dyYXBocWwtaGl2ZS9wbGF0Zm9ybSIKTEFCRUwgb3JnLm9wZW5jb250YWluZXJzLmltYWdlLnNvdXJjZT0iaHR0cHM6Ly9naXRodWIuY29tL2dyYXBocWwtaGl2ZS9wbGF0Zm9ybSIKCkVOVFJZUE9JTlQgWyAiL2VudHJ5cG9pbnQuc2giIF0K",
      "language": "Dockerfile"
    },
    "range": [
      {
        "start": {
          "line": 17
        },
        "end": {
          "line": 17
        }
      }
    ]
  }
],
"composition-federation-2": {
  "buildx.build.provenance": {
    "buildType": "https://mobyproject.org/buildkit@v1",
    "materials": [
      {
        "uri": "pkg:docker/[email protected]?platform=linux%2Farm64",
        "digest": {
          "sha256": "f5a0871ab03b035c58bdb3007c3d177b001c2145c18e81817b71624dcf7d8bff"
        }
      }
    ],
    "invocation": {
      "configSource": {
        "entryPoint": "services.dockerfile"
      },
      "parameters": {
        "frontend": "dockerfile.v0",
        "args": {
          "build-arg:HEALTHCHECK_CMD": "wget --spider -q http://127.0.0.1:${PORT}/_readiness",
          "build-arg:IMAGE_DESCRIPTION": "Federation 2 Composition Service for GraphQL Hive.",
          "build-arg:IMAGE_TITLE": "graphql-hive/composition-federation-2",
          "build-arg:PORT": "3069",
          "build-arg:RELEASE": "6c5c165e29e8253a09fc7b1691a32cd5e8d7c441",
          "build-arg:SERVICE_DIR_NAME": "@hive/external-composition",
          "context:dist": "local:dist",
          "context:shared": "local:shared",
          "frontend.caps": "moby.buildkit.frontend.contexts+forward",
          "local-sessionid:context": "ob3vzrhz5faabymfqxsubkhg3",
          "local-sessionid:dockerfile": "ob3vzrhz5faabymfqxsubkhg3",
          "local-sessionid:shared": "ob3vzrhz5faabymfqxsubkhg3"
        },
        "locals": [
          {
            "name": "context"
          },
          {
            "name": "dist"
          },
          {
            "name": "dockerfile"
          },
          {
            "name": "shared"
          }
        ]
      },
      "environment": {
        "platform": "linux/arm64"
      }
    }
  },
  "buildx.build.ref": "builder-ed970045-9563-45fe-826e-916713cd23dd/builder-ed970045-9563-45fe-826e-916713cd23dd0/j4v2cld0zj2hu70a8dag0t2p6",
  "containerimage.config.digest": "sha256:a9bff32df327def290a7fd471056e9cc79d5a6f3ac3ddf036393b3c71b5c26b8",
  "containerimage.descriptor": {
    "mediaType": "application/vnd.docker.distribution.manifest.v2+json",
    "digest": "sha256:03c5aecb6b0ff55f7a1112c8fc15e946db896229961e68c763d4131aff74fa7c",
    "size": 2075,
    "platform": {
      "architecture": "arm64",
      "os": "linux"
    }
  },
  "containerimage.digest": "sha256:03c5aecb6b0ff55f7a1112c8fc15e946db896229961e68c763d4131aff74fa7c",
  "image.name": "ghcr.io/graphql-hive/composition-federation-2:6c5c165e29e8253a09fc7b1691a32cd5e8d7c441-arm64,ghcr.io/graphql-hive/composition-federation-2:hive_metadata-arm64"
},
"emails": {
  "buildx.build.provenance": {
    "buildType": "https://mobyproject.org/buildkit@v1",
    "materials": [
      {
        "uri": "pkg:docker/[email protected]?platform=linux%2Farm64",
        "digest": {
          "sha256": "f5a0871ab03b035c58bdb3007c3d177b001c2145c18e81817b71624dcf7d8bff"
        }
      }
    ],
    "invocation": {
      "configSource": {
        "entryPoint": "services.dockerfile"
      },
      "parameters": {
        "frontend": "dockerfile.v0",
        "args": {
          "build-arg:HEALTHCHECK_CMD": "wget --spider -q http://127.0.0.1:${PORT}/_readiness",
          "build-arg:IMAGE_DESCRIPTION": "The emails service of the GraphQL Hive project.",
          "build-arg:IMAGE_TITLE": "graphql-hive/emails",
          "build-arg:PORT": "3006",
          "build-arg:RELEASE": "6c5c165e29e8253a09fc7b1691a32cd5e8d7c441",
          "build-arg:SERVICE_DIR_NAME": "@hive/emails",
          "context:dist": "local:dist",
          "context:shared": "local:shared",
          "frontend.caps": "moby.buildkit.frontend.contexts+forward",
          "local-sessionid:context": "ob3vzrhz5faabymfqxsubkhg3",
          "local-sessionid:dockerfile": "ob3vzrhz5faabymfqxsubkhg3",
          "local-sessionid:shared": "ob3vzrhz5faabymfqxsubkhg3"
        },
        "locals": [
          {
            "name": "context"
          },
          {
            "name": "dist"
          },
          {
            "name": "dockerfile"
          },
          {
            "name": "shared"
          }
        ]
      },
      "environment": {
        "platform": "linux/arm64"
      }
    }
  },
  "buildx.build.ref": "builder-ed970045-9563-45fe-826e-916713cd23dd/builder-ed970045-9563-45fe-826e-916713cd23dd0/xlj4d9nyb7l7ss6jgnsz9hame",
  "containerimage.config.digest": "sha256:b81112d19213fe7030584f65e99e0b4e3eccf94d1e9a7a2cb12405c8798fd128",
  "containerimage.descriptor": {
    "mediaType": "application/vnd.docker.distribution.manifest.v2+json",
    "digest": "sha256:3fcacfaa02aaebd318d2f16b24106c81194f53862185cff20a9c0a521f4e51ab",
    "size": 2075,
    "platform": {
      "architecture": "arm64",
      "os": "linux"
    }
  },
  "containerimage.digest": "sha256:3fcacfaa02aaebd318d2f16b24106c81194f53862185cff20a9c0a521f4e51ab",
  "image.name": "ghcr.io/graphql-hive/emails:6c5c165e29e8253a09fc7b1691a32cd5e8d7c441-arm64,ghcr.io/graphql-hive/emails:hive_metadata-arm64"
},
"policy": {
  "buildx.build.provenance": {
    "buildType": "https://mobyproject.org/buildkit@v1",
    "materials": [
      {
        "uri": "pkg:docker/[email protected]?platform=linux%2Farm64",
        "digest": {
          "sha256": "f5a0871ab03b035c58bdb3007c3d177b001c2145c18e81817b71624dcf7d8bff"
        }
      }
    ],
    "invocation": {
      "configSource": {
        "entryPoint": "services.dockerfile"
      },
      "parameters": {
        "frontend": "dockerfile.v0",
        "args": {
          "build-arg:HEALTHCHECK_CMD": "wget --spider -q http://127.0.0.1:${PORT}/_readiness",
          "build-arg:IMAGE_DESCRIPTION": "The policy service of the GraphQL Hive project.",
          "build-arg:IMAGE_TITLE": "graphql-hive/policy",
          "build-arg:PORT": "3012",
          "build-arg:RELEASE": "6c5c165e29e8253a09fc7b1691a32cd5e8d7c441",
          "build-arg:SERVICE_DIR_NAME": "@hive/policy",
          "context:dist": "local:dist",
          "context:shared": "local:shared",
          "frontend.caps": "moby.buildkit.frontend.contexts+forward",
          "local-sessionid:context": "ob3vzrhz5faabymfqxsubkhg3",
          "local-sessionid:dockerfile": "ob3vzrhz5faabymfqxsubkhg3",
          "local-sessionid:shared": "ob3vzrhz5faabymfqxsubkhg3"
        },
        "locals": [
          {
            "name": "context"
          },
          {
            "name": "dist"
          },
          {
            "name": "dockerfile"
          },
          {
            "name": "shared"
          }
        ]
      },
      "environment": {
        "platform": "linux/arm64"
      }
    }
  },
  "buildx.build.ref": "builder-ed970045-9563-45fe-826e-916713cd23dd/builder-ed970045-9563-45fe-826e-916713cd23dd0/frps5jjy0o686owoahevddj2e",
  "containerimage.config.digest": "sha256:d618cd61d988dbbc4458a5118d830d97eea63410c1d9e4e138aebf5339827ad7",
  "containerimage.descriptor": {
    "mediaType": "application/vnd.docker.distribution.manifest.v2+json",
    "digest": "sha256:22a98746f3b4bcf886436c8964612626e5544cbe481721dc5394bf15360106ad",
    "size": 2075,
    "platform": {
      "architecture": "arm64",
      "os": "linux"
    }
  },
  "containerimage.digest": "sha256:22a98746f3b4bcf886436c8964612626e5544cbe481721dc5394bf15360106ad",
  "image.name": "ghcr.io/graphql-hive/policy:6c5c165e29e8253a09fc7b1691a32cd5e8d7c441-arm64,ghcr.io/graphql-hive/policy:hive_metadata-arm64"
},
"rate-limit": {
  "buildx.build.provenance": {
    "buildType": "https://mobyproject.org/buildkit@v1",
    "materials": [
      {
        "uri": "pkg:docker/[email protected]?platform=linux%2Farm64",
        "digest": {
          "sha256": "f5a0871ab03b035c58bdb3007c3d177b001c2145c18e81817b71624dcf7d8bff"
        }
      }
    ],
    "invocation": {
      "configSource": {
        "entryPoint": "services.dockerfile"
      },
      "parameters": {
        "frontend": "dockerfile.v0",
        "args": {
          "build-arg:HEALTHCHECK_CMD": "wget --spider -q http://127.0.0.1:${PORT}/_readiness",
          "build-arg:IMAGE_DESCRIPTION": "The rate limit service of the GraphQL Hive project.",
          "build-arg:IMAGE_TITLE": "graphql-hive/rate-limit",
          "build-arg:PORT": "3009",
          "build-arg:RELEASE": "6c5c165e29e8253a09fc7b1691a32cd5e8d7c441",
          "build-arg:SERVICE_DIR_NAME": "@hive/rate-limit",
          "context:dist": "local:dist",
          "context:shared": "local:shared",
          "frontend.caps": "moby.buildkit.frontend.contexts+forward",
          "local-sessionid:context": "ob3vzrhz5faabymfqxsubkhg3",
          "local-sessionid:dockerfile": "ob3vzrhz5faabymfqxsubkhg3",
          "local-sessionid:shared": "ob3vzrhz5faabymfqxsubkhg3"
        },
        "locals": [
          {
            "name": "context"
          },
          {
            "name": "dist"
          },
          {
            "name": "dockerfile"
          },
          {
            "name": "shared"
          }
        ]
      },
      "environment": {
        "platform": "linux/arm64"
      }
    }
  },
  "buildx.build.ref": "builder-ed970045-9563-45fe-826e-916713cd23dd/builder-ed970045-9563-45fe-826e-916713cd23dd0/xio2nr3lh21vcdvp5dbbykul9",
  "containerimage.config.digest": "sha256:dc0762016436ca5e796427ffd823d320c239f956857e8d9ed8a67e97585b889e",
  "containerimage.descriptor": {
    "mediaType": "application/vnd.docker.distribution.manifest.v2+json",
    "digest": "sha256:ffdd0c80ecf389e36dcc82f29dcf34f16c1edeb1fa0e52adf295244701471ea5",
    "size": 2075,
    "platform": {
      "architecture": "arm64",
      "os": "linux"
    }
  },
  "containerimage.digest": "sha256:ffdd0c80ecf389e36dcc82f29dcf34f16c1edeb1fa0e52adf295244701471ea5",
  "image.name": "ghcr.io/graphql-hive/rate-limit:6c5c165e29e8253a09fc7b1691a32cd5e8d7c441-arm64,ghcr.io/graphql-hive/rate-limit:hive_metadata-arm64"
},
"schema": {
  "buildx.build.provenance": {
    "buildType": "https://mobyproject.org/buildkit@v1",
    "materials": [
      {
        "uri": "pkg:docker/[email protected]?platform=linux%2Farm64",
        "digest": {
          "sha256": "f5a0871ab03b035c58bdb3007c3d177b001c2145c18e81817b71624dcf7d8bff"
        }
      }
    ],
    "invocation": {
      "configSource": {
        "entryPoint": "services.dockerfile"
      },
      "parameters": {
        "frontend": "dockerfile.v0",
        "args": {
          "build-arg:HEALTHCHECK_CMD": "wget --spider -q http://127.0.0.1:${PORT}/_readiness",
          "build-arg:IMAGE_DESCRIPTION": "The schema service of the GraphQL Hive project.",
          "build-arg:IMAGE_TITLE": "graphql-hive/schema",
          "build-arg:PORT": "3002",
          "build-arg:RELEASE": "6c5c165e29e8253a09fc7b1691a32cd5e8d7c441",
          "build-arg:SERVICE_DIR_NAME": "@hive/schema",
          "context:dist": "local:dist",
          "context:shared": "local:shared",
          "frontend.caps": "moby.buildkit.frontend.contexts+forward",
          "local-sessionid:context": "ob3vzrhz5faabymfqxsubkhg3",
          "local-sessionid:dockerfile": "ob3vzrhz5faabymfqxsubkhg3",
          "local-sessionid:shared": "ob3vzrhz5faabymfqxsubkhg3"
        },
        "locals": [
          {
            "name": "context"
          },
          {
            "name": "dist"
          },
          {
            "name": "dockerfile"
          },
          {
            "name": "shared"
          }
        ]
      },
      "environment": {
        "platform": "linux/arm64"
      }
    }
  },
  "buildx.build.ref": "builder-ed970045-9563-45fe-826e-916713cd23dd/builder-ed970045-9563-45fe-826e-916713cd23dd0/ilssxw3a9xl28twc6permsas4",
  "containerimage.config.digest": "sha256:901fce9fd847c86e9a88876f41e4d4e88077ffdde7055d99578e3d085ce85808",
  "containerimage.descriptor": {
    "mediaType": "application/vnd.docker.distribution.manifest.v2+json",
    "digest": "sha256:2e35e45c0557fcba3d979c814aac1340300df2f995639dbae5136fa6911f9dee",
    "size": 2075,
    "platform": {
      "architecture": "arm64",
      "os": "linux"
    }
  },
  "containerimage.digest": "sha256:2e35e45c0557fcba3d979c814aac1340300df2f995639dbae5136fa6911f9dee",
  "image.name": "ghcr.io/graphql-hive/schema:6c5c165e29e8253a09fc7b1691a32cd5e8d7c441-arm64,ghcr.io/graphql-hive/schema:hive_metadata-arm64"
},
"server": {
  "buildx.build.provenance": {
    "buildType": "https://mobyproject.org/buildkit@v1",
    "materials": [
      {
        "uri": "pkg:docker/[email protected]?platform=linux%2Farm64",
        "digest": {
          "sha256": "f5a0871ab03b035c58bdb3007c3d177b001c2145c18e81817b71624dcf7d8bff"
        }
      }
    ],
    "invocation": {
      "configSource": {
        "entryPoint": "services.dockerfile"
      },
      "parameters": {
        "frontend": "dockerfile.v0",
        "args": {
          "build-arg:HEALTHCHECK_CMD": "wget --spider -q http://127.0.0.1:${PORT}/_readiness",
          "build-arg:IMAGE_DESCRIPTION": "The server service of the GraphQL Hive project.",
          "build-arg:IMAGE_TITLE": "graphql-hive/server",
          "build-arg:PORT": "3001",
          "build-arg:RELEASE": "6c5c165e29e8253a09fc7b1691a32cd5e8d7c441",
          "build-arg:SERVICE_DIR_NAME": "@hive/server",
          "context:dist": "local:dist",
          "context:shared": "local:shared",
          "frontend.caps": "moby.buildkit.frontend.contexts+forward",
          "local-sessionid:context": "ob3vzrhz5faabymfqxsubkhg3",
          "local-sessionid:dockerfile": "ob3vzrhz5faabymfqxsubkhg3",
          "local-sessionid:shared": "ob3vzrhz5faabymfqxsubkhg3"
        },
        "locals": [
          {
            "name": "context"
          },
          {
            "name": "dist"
          },
          {
            "name": "dockerfile"
          },
          {
            "name": "shared"
          }
        ]
      },
      "environment": {
        "platform": "linux/arm64"
      }
    }
  },
  "buildx.build.ref": "builder-ed970045-9563-45fe-826e-916713cd23dd/builder-ed970045-9563-45fe-826e-916713cd23dd0/qwivq5trdecvj0o8jguuaawng",
  "containerimage.config.digest": "sha256:765f46069cf683b7674b3490568b98480f0589db569b7f4fd9ceef04477ff8de",
  "containerimage.descriptor": {
    "mediaType": "application/vnd.docker.distribution.manifest.v2+json",
    "digest": "sha256:f02ec649afe15d0a9b203ded6ee5ad00cfad80e7e393c3b14bab7d9e0a189b34",
    "size": 2076,
    "platform": {
      "architecture": "arm64",
      "os": "linux"
    }
  },
  "containerimage.digest": "sha256:f02ec649afe15d0a9b203ded6ee5ad00cfad80e7e393c3b14bab7d9e0a189b34",
  "image.name": "ghcr.io/graphql-hive/server:6c5c165e29e8253a09fc7b1691a32cd5e8d7c441-arm64,ghcr.io/graphql-hive/server:hive_metadata-arm64"
},
"storage": {
  "buildx.build.provenance": {
    "buildType": "https://mobyproject.org/buildkit@v1",
    "materials": [
      {
        "uri": "pkg:docker/[email protected]?platform=linux%2Farm64",
        "digest": {
          "sha256": "f5a0871ab03b035c58bdb3007c3d177b001c2145c18e81817b71624dcf7d8bff"
        }
      }
    ],
    "invocation": {
      "configSource": {
        "entryPoint": "migrations.dockerfile"
      },
      "parameters": {
        "frontend": "dockerfile.v0",
        "args": {
          "build-arg:IMAGE_DESCRIPTION": "The migrations service of the GraphQL Hive project.",
          "build-arg:IMAGE_TITLE": "graphql-hive/storage",
          "build-arg:RELEASE": "6c5c165e29e8253a09fc7b1691a32cd5e8d7c441",
          "context:dist": "local:dist",
          "context:shared": "local:shared",
          "frontend.caps": "moby.buildkit.frontend.contexts+forward",
          "local-sessionid:context": "ob3vzrhz5faabymfqxsubkhg3",
          "local-sessionid:dockerfile": "ob3vzrhz5faabymfqxsubkhg3",
          "local-sessionid:shared": "ob3vzrhz5faabymfqxsubkhg3"
        },
        "locals": [
          {
            "name": "context"
          },
          {
            "name": "dist"
          },
          {
            "name": "dockerfile"
          },
          {
            "name": "shared"
          }
        ]
      },
      "environment": {
        "platform": "linux/arm64"
      }
    }
  },
  "buildx.build.ref": "builder-ed970045-9563-45fe-826e-916713cd23dd/builder-ed970045-9563-45fe-826e-916713cd23dd0/pn4g906tix9ipp1892dt41nb5",
  "containerimage.config.digest": "sha256:13d4e9df2de2b109726e94960d24c832cf9ecb0cbc24cdc2b111921a9482ff11",
  "containerimage.descriptor": {
    "mediaType": "application/vnd.docker.distribution.manifest.v2+json",
    "digest": "sha256:98f97ab335d850ff325cfb2149ed716868178fb1c4934d4caa29156bec515550",
    "size": 2075,
    "platform": {
      "architecture": "arm64",
      "os": "linux"
    }
  },
  "containerimage.digest": "sha256:98f97ab335d850ff325cfb2149ed716868178fb1c4934d4caa29156bec515550",
  "image.name": "ghcr.io/graphql-hive/storage:6c5c165e29e8253a09fc7b1691a32cd5e8d7c441-arm64,ghcr.io/graphql-hive/storage:hive_metadata-arm64"
},
"stripe-billing": {
  "buildx.build.provenance": {
    "buildType": "https://mobyproject.org/buildkit@v1",
    "materials": [
      {
        "uri": "pkg:docker/[email protected]?platform=linux%2Farm64",
        "digest": {
          "sha256": "f5a0871ab03b035c58bdb3007c3d177b001c2145c18e81817b71624dcf7d8bff"
        }
      }
    ],
    "invocation": {
      "configSource": {
        "entryPoint": "services.dockerfile"
      },
      "parameters": {
        "frontend": "dockerfile.v0",
        "args": {
          "build-arg:HEALTHCHECK_CMD": "wget --spider -q http://127.0.0.1:${PORT}/_readiness",
          "build-arg:IMAGE_DESCRIPTION": "The stripe billing service of the GraphQL Hive project.",
          "build-arg:IMAGE_TITLE": "graphql-hive/stripe-billing",
          "build-arg:PORT": "3010",
          "build-arg:RELEASE": "6c5c165e29e8253a09fc7b1691a32cd5e8d7c441",
          "build-arg:SERVICE_DIR_NAME": "@hive/stripe-billing",
          "context:dist": "local:dist",
          "context:shared": "local:shared",
          "frontend.caps": "moby.buildkit.frontend.contexts+forward",
          "local-sessionid:context": "ob3vzrhz5faabymfqxsubkhg3",
          "local-sessionid:dockerfile": "ob3vzrhz5faabymfqxsubkhg3",
          "local-sessionid:shared": "ob3vzrhz5faabymfqxsubkhg3"
        },
        "locals": [
          {
            "name": "context"
          },
          {
            "name": "dist"
          },
          {
            "name": "dockerfile"
          },
          {
            "name": "shared"
          }
        ]
      },
      "environment": {
        "platform": "linux/arm64"
      }
    }
  },
  "buildx.build.ref": "builder-ed970045-9563-45fe-826e-916713cd23dd/builder-ed970045-9563-45fe-826e-916713cd23dd0/whh2t1dcamjcpplja9uo3o0r6",
  "containerimage.config.digest": "sha256:00041269b7b87e9410137d476cde2083204046310d943f0e5068326d90a52d27",
  "containerimage.descriptor": {
    "mediaType": "application/vnd.docker.distribution.manifest.v2+json",
    "digest": "sha256:ac44455dfaad5cab11316a7a23c22b3c0fe8d46fe790acb44f096b478a62fafb",
    "size": 2075,
    "platform": {
      "architecture": "arm64",
      "os": "linux"
    }
  },
  "containerimage.digest": "sha256:ac44455dfaad5cab11316a7a23c22b3c0fe8d46fe790acb44f096b478a62fafb",
  "image.name": "ghcr.io/graphql-hive/stripe-billing:6c5c165e29e8253a09fc7b1691a32cd5e8d7c441-arm64,ghcr.io/graphql-hive/stripe-billing:hive_metadata-arm64"
},
"tokens": {
  "buildx.build.provenance": {
    "buildType": "https://mobyproject.org/buildkit@v1",
    "materials": [
      {
        "uri": "pkg:docker/[email protected]?platform=linux%2Farm64",
        "digest": {
          "sha256": "f5a0871ab03b035c58bdb3007c3d177b001c2145c18e81817b71624dcf7d8bff"
        }
      }
    ],
    "invocation": {
      "configSource": {
        "entryPoint": "services.dockerfile"
      },
      "parameters": {
        "frontend": "dockerfile.v0",
        "args": {
          "build-arg:HEALTHCHECK_CMD": "wget --spider -q http://127.0.0.1:${PORT}/_readiness",
          "build-arg:IMAGE_DESCRIPTION": "The tokens service of the GraphQL Hive project.",
          "build-arg:IMAGE_TITLE": "graphql-hive/tokens",
          "build-arg:PORT": "3003",
          "build-arg:RELEASE": "6c5c165e29e8253a09fc7b1691a32cd5e8d7c441",
          "build-arg:SERVICE_DIR_NAME": "@hive/tokens",
          "context:dist": "local:dist",
          "context:shared": "local:shared",
          "frontend.caps": "moby.buildkit.frontend.contexts+forward",
          "local-sessionid:context": "ob3vzrhz5faabymfqxsubkhg3",
          "local-sessionid:dockerfile": "ob3vzrhz5faabymfqxsubkhg3",
          "local-sessionid:shared": "ob3vzrhz5faabymfqxsubkhg3"
        },
        "locals": [
          {
            "name": "context"
          },
          {
            "name": "dist"
          },
          {
            "name": "dockerfile"
          },
          {
            "name": "shared"
          }
        ]
      },
      "environment": {
        "platform": "linux/arm64"
      }
    }
  },
  "buildx.build.ref": "builder-ed970045-9563-45fe-826e-916713cd23dd/builder-ed970045-9563-45fe-826e-916713cd23dd0/3zbla3ufq1bfgpor8jvuwexsf",
  "containerimage.config.digest": "sha256:66a2a60b93c5df6cb862a05e7fe4d01b4d28d8ab978307d6ca7f38c40ad427ed",
  "containerimage.descriptor": {
    "mediaType": "application/vnd.docker.distribution.manifest.v2+json",
    "digest": "sha256:2ac579a819ad7539d36792aa50b135931dbf4a888fb7b3c87aaf1e8cd6b700ac",
    "size": 2075,
    "platform": {
      "architecture": "arm64",
      "os": "linux"
    }
  },
  "containerimage.digest": "sha256:2ac579a819ad7539d36792aa50b135931dbf4a888fb7b3c87aaf1e8cd6b700ac",
  "image.name": "ghcr.io/graphql-hive/tokens:6c5c165e29e8253a09fc7b1691a32cd5e8d7c441-arm64,ghcr.io/graphql-hive/tokens:hive_metadata-arm64"
},
"usage": {
  "buildx.build.provenance": {
    "buildType": "https://mobyproject.org/buildkit@v1",
    "materials": [
      {
        "uri": "pkg:docker/[email protected]?platform=linux%2Farm64",
        "digest": {
          "sha256": "f5a0871ab03b035c58bdb3007c3d177b001c2145c18e81817b71624dcf7d8bff"
        }
      }
    ],
    "invocation": {
      "configSource": {
        "entryPoint": "services.dockerfile"
      },
      "parameters": {
        "frontend": "dockerfile.v0",
        "args": {
          "build-arg:HEALTHCHECK_CMD": "wget --spider -q http://127.0.0.1:${PORT}/_readiness",
          "build-arg:IMAGE_DESCRIPTION": "The usage ingestor service of the GraphQL Hive project.",
          "build-arg:IMAGE_TITLE": "graphql-hive/usage",
          "build-arg:PORT": "3006",
          "build-arg:RELEASE": "6c5c165e29e8253a09fc7b1691a32cd5e8d7c441",
          "build-arg:SERVICE_DIR_NAME": "@hive/usage",
          "context:dist": "local:dist",
          "context:shared": "local:shared",
          "frontend.caps": "moby.buildkit.frontend.contexts+forward",
          "local-sessionid:context": "ob3vzrhz5faabymfqxsubkhg3",
          "local-sessionid:dockerfile": "ob3vzrhz5faabymfqxsubkhg3",
          "local-sessionid:shared": "ob3vzrhz5faabymfqxsubkhg3"
        },
        "locals": [
          {
            "name": "context"
          },
          {
            "name": "dist"
          },
          {
            "name": "dockerfile"
          },
          {
            "name": "shared"
          }
        ]
      },
      "environment": {
        "platform": "linux/arm64"
      }
    }
  },
  "buildx.build.ref": "builder-ed970045-9563-45fe-826e-916713cd23dd/builder-ed970045-9563-45fe-826e-916713cd23dd0/ehtjdodtvlpb0blsdr12pk09t",
  "containerimage.config.digest": "sha256:689f092860f58f859b3a5858bd2a980d51e56d0309064b6e9099ef94884ac768",
  "containerimage.descriptor": {
    "mediaType": "application/vnd.docker.distribution.manifest.v2+json",
    "digest": "sha256:28494a16c4bb30dced3baeed789500350f0e223be3f0ee536a1c202cb32e34a2",
    "size": 2075,
    "platform": {
      "architecture": "arm64",
      "os": "linux"
    }
  },
  "containerimage.digest": "sha256:28494a16c4bb30dced3baeed789500350f0e223be3f0ee536a1c202cb32e34a2",
  "image.name": "ghcr.io/graphql-hive/usage:6c5c165e29e8253a09fc7b1691a32cd5e8d7c441-arm64,ghcr.io/graphql-hive/usage:hive_metadata-arm64"
},
"usage-estimator": {
  "buildx.build.provenance": {
    "buildType": "https://mobyproject.org/buildkit@v1",
    "materials": [
      {
        "uri": "pkg:docker/[email protected]?platform=linux%2Farm64",
        "digest": {
          "sha256": "f5a0871ab03b035c58bdb3007c3d177b001c2145c18e81817b71624dcf7d8bff"
        }
      }
    ],
    "invocation": {
      "configSource": {
        "entryPoint": "services.dockerfile"
      },
      "parameters": {
        "frontend": "dockerfile.v0",
        "args": {
          "build-arg:HEALTHCHECK_CMD": "wget --spider -q http://127.0.0.1:${PORT}/_readiness",
          "build-arg:IMAGE_DESCRIPTION": "The usage estimator service of the GraphQL Hive project.",
          "build-arg:IMAGE_TITLE": "graphql-hive/usage-estimator",
          "build-arg:PORT": "3008",
          "build-arg:RELEASE": "6c5c165e29e8253a09fc7b1691a32cd5e8d7c441",
          "build-arg:SERVICE_DIR_NAME": "@hive/usage-estimator",
          "context:dist": "local:dist",
          "context:shared": "local:shared",
          "frontend.caps": "moby.buildkit.frontend.contexts+forward",
          "local-sessionid:context": "ob3vzrhz5faabymfqxsubkhg3",
          "local-sessionid:dockerfile": "ob3vzrhz5faabymfqxsubkhg3",
          "local-sessionid:shared": "ob3vzrhz5faabymfqxsubkhg3"
        },
        "locals": [
          {
            "name": "context"
          },
          {
            "name": "dist"
          },
          {
            "name": "dockerfile"
          },
          {
            "name": "shared"
          }
        ]
      },
      "environment": {
        "platform": "linux/arm64"
      }
    }
  },
  "buildx.build.ref": "builder-ed970045-9563-45fe-826e-916713cd23dd/builder-ed970045-9563-45fe-826e-916713cd23dd0/x3k3w5eqq51lpmpkdrnwqnp4a",
  "containerimage.config.digest": "sha256:732552df508fea1f4346e92b87befd09571d0e3fc435d7f302f41a5f5336718d",
  "containerimage.descriptor": {
    "mediaType": "application/vnd.docker.distribution.manifest.v2+json",
    "digest": "sha256:39e6e8ef86dd6a7800f8d619a9e6c7ec70b272d73819d8cb18d693f18051e319",
    "size": 2075,
    "platform": {
      "architecture": "arm64",
      "os": "linux"
    }
  },
  "containerimage.digest": "sha256:39e6e8ef86dd6a7800f8d619a9e6c7ec70b272d73819d8cb18d693f18051e319",
  "image.name": "ghcr.io/graphql-hive/usage-estimator:6c5c165e29e8253a09fc7b1691a32cd5e8d7c441-arm64,ghcr.io/graphql-hive/usage-estimator:hive_metadata-arm64"
},
"usage-ingestor": {
  "buildx.build.provenance": {
    "buildType": "https://mobyproject.org/buildkit@v1",
    "materials": [
      {
        "uri": "pkg:docker/[email protected]?platform=linux%2Farm64",
        "digest": {
          "sha256": "f5a0871ab03b035c58bdb3007c3d177b001c2145c18e81817b71624dcf7d8bff"
        }
      }
    ],
    "invocation": {
      "configSource": {
        "entryPoint": "services.dockerfile"
      },
      "parameters": {
        "frontend": "dockerfile.v0",
        "args": {
          "build-arg:HEALTHCHECK_CMD": "wget --spider -q http://127.0.0.1:${PORT}/_readiness",
          "build-arg:IMAGE_DESCRIPTION": "The usage ingestor service of the GraphQL Hive project.",
          "build-arg:IMAGE_TITLE": "graphql-hive/usage-ingestor",
          "build-arg:PORT": "3007",
          "build-arg:RELEASE": "6c5c165e29e8253a09fc7b1691a32cd5e8d7c441",
          "build-arg:SERVICE_DIR_NAME": "@hive/usage-ingestor",
          "context:dist": "local:dist",
          "context:shared": "local:shared",
          "frontend.caps": "moby.buildkit.frontend.contexts+forward",
          "local-sessionid:context": "ob3vzrhz5faabymfqxsubkhg3",
          "local-sessionid:dockerfile": "ob3vzrhz5faabymfqxsubkhg3",
          "local-sessionid:shared": "ob3vzrhz5faabymfqxsubkhg3"
        },
        "locals": [
          {
            "name": "context"
          },
          {
            "name": "dist"
          },
          {
            "name": "dockerfile"
          },
          {
            "name": "shared"
          }
        ]
      },
      "environment": {
        "platform": "linux/arm64"
      }
    }
  },
  "buildx.build.ref": "builder-ed970045-9563-45fe-826e-916713cd23dd/builder-ed970045-9563-45fe-826e-916713cd23dd0/iw4w1mco5vqtzwyc45oexea85",
  "containerimage.config.digest": "sha256:71644f1c6e17e2a9f8595b4e62cd9b5a12842029e2643df569d3d74acef2caaa",
  "containerimage.descriptor": {
    "mediaType": "application/vnd.docker.distribution.manifest.v2+json",
    "digest": "sha256:d6e8c895690fedfb7db6a3888d4a6cd2bc5c197fa9a490edc331dc942bba88a3",
    "size": 2075,
    "platform": {
      "architecture": "arm64",
      "os": "linux"
    }
  },
  "containerimage.digest": "sha256:d6e8c895690fedfb7db6a3888d4a6cd2bc5c197fa9a490edc331dc942bba88a3",
  "image.name": "ghcr.io/graphql-hive/usage-ingestor:6c5c165e29e8253a09fc7b1691a32cd5e8d7c441-arm64,ghcr.io/graphql-hive/usage-ingestor:hive_metadata-arm64"
},
"webhooks": {
  "buildx.build.provenance": {
    "buildType": "https://mobyproject.org/buildkit@v1",
    "materials": [
      {
        "uri": "pkg:docker/[email protected]?platform=linux%2Farm64",
        "digest": {
          "sha256": "f5a0871ab03b035c58bdb3007c3d177b001c2145c18e81817b71624dcf7d8bff"
        }
      }
    ],
    "invocation": {
      "configSource": {
        "entryPoint": "services.dockerfile"
      },
      "parameters": {
        "frontend": "dockerfile.v0",
        "args": {
          "build-arg:HEALTHCHECK_CMD": "wget --spider -q http://127.0.0.1:${PORT}/_readiness",
          "build-arg:IMAGE_DESCRIPTION": "The webhooks ingestor service of the GraphQL Hive project.",
          "build-arg:IMAGE_TITLE": "graphql-hive/webhooks",
          "build-arg:PORT": "3005",
          "build-arg:RELEASE": "6c5c165e29e8253a09fc7b1691a32cd5e8d7c441",
          "build-arg:SERVICE_DIR_NAME": "@hive/webhooks",
          "context:dist": "local:dist",
          "context:shared": "local:shared",
          "frontend.caps": "moby.buildkit.frontend.contexts+forward",
          "local-sessionid:context": "ob3vzrhz5faabymfqxsubkhg3",
          "local-sessionid:dockerfile": "ob3vzrhz5faabymfqxsubkhg3",
          "local-sessionid:shared": "ob3vzrhz5faabymfqxsubkhg3"
        },
        "locals": [
          {
            "name": "context"
          },
          {
            "name": "dist"
          },
          {
            "name": "dockerfile"
          },
          {
            "name": "shared"
          }
        ]
      },
      "environment": {
        "platform": "linux/arm64"
      }
    }
  },
  "buildx.build.ref": "builder-ed970045-9563-45fe-826e-916713cd23dd/builder-ed970045-9563-45fe-826e-916713cd23dd0/l5z0bcm5625sftpjxkfljonpe",
  "containerimage.config.digest": "sha256:30fd796887be06c86d58b5b5883a113cdc0df670b6e048ebafe7e223974f51a9",
  "containerimage.descriptor": {
    "mediaType": "application/vnd.docker.distribution.manifest.v2+json",
    "digest": "sha256:958f09a6b4e30a48a28720490f1dd7e03959208c73899a75b142804f28074476",
    "size": 2075,
    "platform": {
      "architecture": "arm64",
      "os": "linux"
    }
  },
  "containerimage.digest": "sha256:958f09a6b4e30a48a28720490f1dd7e03959208c73899a75b142804f28074476",
  "image.name": "ghcr.io/graphql-hive/webhooks:6c5c165e29e8253a09fc7b1691a32cd5e8d7c441-arm64,ghcr.io/graphql-hive/webhooks:hive_metadata-arm64"
}
}

Copy link
Contributor

github-actions bot commented Feb 16, 2025

🚀 Snapshot Release (alpha)

The latest changes of this PR are available as alpha on npm (based on the declared changesets):

Package Version Info
hive 5.1.2-alpha-20250220210705-6c5c165e29e8253a09fc7b1691a32cd5e8d7c441 npm ↗︎ unpkg ↗︎

@jdolle
Copy link
Collaborator Author

jdolle commented Feb 16, 2025

I've adjusted this to save metadata to a column on the schemaVersion table.
Since we're capable of parsing the SDL at runtime, looking up the metadata in an object should be no worse.

@jdolle jdolle marked this pull request as ready for review February 16, 2025 01:06
Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 3

🧹 Nitpick comments (24)
packages/services/schema/src/lib/metadata-extraction.ts (3)

34-37: Clarify rationale for copying metadata to fields.

The comment explains that copying metadata to fields is partly due to inheritance info being lost after composition. Consider adding a reference to where composition discards or complicates inheritance, so future maintainers can easily track why the metadata copy is essential.


77-88: Deduplicate metadata during merging.

The mergeMetadata function appends metadata arrays across subgraphs. If subgraphs contain the same metadata entries, duplicates will accumulate. Consider adding a check to filter out duplicates when merging to avoid clutter and keep the metadata clean.

+      combined[coordinate] = existing === undefined
+        ? metadata
+        : [
+            ...existing,
+            ...metadata.filter(
+              newItem => !existing.some(
+                existingItem =>
+                  existingItem.name === newItem.name &&
+                  existingItem.content === newItem.content
+              )
+            )
+          ];

12-75: Add unit tests for metadata extraction.

Although the logic for extractMetadata is sound, unit tests can ensure that corner cases (e.g., missing arguments, unknown directive names, unusual AST shapes) are handled properly and do not regress.

integration-tests/tests/api/schema/publish.spec.ts (1)

4085-4085: Consider initializing or describing the new 'schemaMetadata' field.

Currently, this property is set to null without any accompanying comment or usage. If it's meant to store important data later, consider adding a short comment describing its expected usage or assigning a default non-null value if appropriate, ensuring future clarity around its purpose.

packages/services/storage/src/index.ts (1)

4711-4715: Consider length constraints or additional checks for content.

Defining SchemaMetadataModel is clear and organized. However, if content can be very large, you may want to add a max length or sanitization checks to mitigate potential payload bloat or injection concerns.

Example optional diff:

 const SchemaMetadataModel = zod.object({
   name: zod.string(),
-  content: zod.string(),
+  content: zod.string().max(2048, "Content length cannot exceed 2048 characters"),
 });
packages/services/schema/src/types.ts (1)

19-19: Consider enhancing type safety for schema metadata.

While the current type is functional, consider these improvements for better type safety and maintainability:

  1. Define a dedicated interface for the metadata object
  2. Consider using string literals for known metadata names
  3. Add JSDoc comments to document the purpose and structure

Example:

/** Metadata object containing name and content pairs */
interface SchemaMetadataItem {
  /** Name of the metadata entry */
  name: string;
  /** Content of the metadata entry */
  content: string;
}

/** Known metadata names */
type MetadataName = 'description' | 'deprecated' | 'example' | string;

/** Schema metadata mapping */
type SchemaMetadataMapping = Record<string, Array<SchemaMetadataItem>>;
packages/libraries/federation-link-utils/src/index.ts (1)

52-52: Simplify the import name resolution.

The current implementation can be made more concise using string methods.

-        return name.startsWith('@') ? name.substring(1) : name;
+        return name.replace(/^@/, '');
integration-tests/tests/schema/metadata.spec.ts (3)

5-16: Consider extracting client setup to a shared utility.

The TRPC client setup could be reused across other integration tests.

Consider moving this to a shared utility file:

// testkit/utils/trpc.ts
export function createSchemaClient() {
  const host = process.env['SCHEMA_SERVICE_HOST_OVERRIDE'] ||
    (await getServiceHost('schema', 3002).then(r => `http://${r}`));
  
  return createTRPCProxyClient<SchemaBuilderApi>({
    links: [
      httpLink({
        url: host + `/trpc`,
        fetch,
      }),
    ],
  });
}

18-62: Enhance test coverage for metadata processing.

While the current test verifies basic metadata processing, consider adding tests for:

  1. Invalid metadata values
  2. Multiple metadata directives on the same entity
  3. Metadata inheritance in extended types

63-150: Consider using explicit assertions instead of snapshots.

While snapshots are convenient, explicit assertions would make the test intentions clearer and more maintainable.

Example:

expect(result.supergraph).toMatch(/@meta\(name: "owner", content: "hive-team"\)/);
expect(result.supergraph).toMatch(/directive @meta\(name: String!, content: String!\)/);
packages/web/app/src/components/target/explorer/super-graph-metadata.tsx (2)

26-32: Remove redundant fragment.

The fragment is unnecessary since it only contains a single mapping operation.

-        <>
          {props.supergraphMetadata.map((m, i) => (
            <div key={i}>
              <span className="font-bold">{m.name}:</span> {m.content}
            </div>
          ))}
-        </>
🧰 Tools
🪛 Biome (1.9.4)

[error] 26-32: Avoid using unnecessary Fragment.

A fragment is redundant if it contains only one child, or if it is the child of a html element, and is not a keyed fragment.
Unsafe fix: Remove the Fragment

(lint/complexity/noUselessFragments)


35-35: Extract icon styles to a CSS class.

Consider moving the inline styles to a CSS class for better maintainability.

-      <MetadataIcon className="my-[5px] cursor-pointer text-white" />
+      <MetadataIcon className="metadata-icon" />

Add to your CSS:

.metadata-icon {
  margin-top: 5px;
  margin-bottom: 5px;
  cursor: pointer;
  color: white;
}
packages/libraries/federation-link-utils/src/link.ts (1)

13-13: Use type guard for safer type casting.

Instead of type assertion, consider using a type guard for safer type casting of the feature value.

-    const url = FederatedLinkUrl.fromUrl((feature.value as StringValueNode).value);
+    if (feature.value.kind === Kind.STRING) {
+      const url = FederatedLinkUrl.fromUrl(feature.value.value);
+      return new FederatedLink(url, null, []);
+    }
+    return;
packages/services/api/src/modules/schema/resolvers/SchemaVersion.ts (1)

88-88: Track GraphQL resolve info optimization TODO.

The TODO comment about using GraphQL resolve info for query optimization should be tracked.

Would you like me to create an issue to track this optimization task?

packages/services/api/src/shared/entities.ts (1)

390-390: Consider using a more specific type for schema metadata.

While the current implementation works, using a Record with 'any' type could make the code harder to maintain. Consider creating a dedicated type for metadata content.

-  schemaMetadata: Record<string, Array<{ name: string; content: string }>> | null;
+  schemaMetadata: Record<string, Array<SchemaMetadataMapper>> | null;
packages/services/api/src/modules/schema/providers/models/shared.ts (1)

243-243: LGTM! Consider adding JSDoc comments.

The schemaMetadata property is well-typed and properly structured. Consider adding JSDoc comments to document the purpose and structure of the metadata.

+  /**
+   * Metadata associated with schema coordinates.
+   * @property {string} name - The name of the metadata entry
+   * @property {string} content - The content of the metadata entry
+   */
   schemaMetadata: null | Record<string, Array<{ name: string; content: string }>>;

Also applies to: 290-290

packages/services/api/src/modules/schema/resolvers/SchemaExplorer.ts (1)

63-65: Consider extracting the common getFieldMetadata implementation.

The getFieldMetadata implementation is duplicated across multiple types. Consider extracting it into a shared utility function to improve maintainability.

+ function createFieldMetadataGetter(supergraph: SuperGraphInformation, typeName: string) {
+   return (fieldName: string) =>
+     supergraph.schemaCoordinateMetadataMappings?.get(`${typeName}.${fieldName}`) ?? null;
+ }

  // Usage example:
  supergraph: supergraph
    ? {
-       getFieldMetadata: fieldName =>
-         supergraph.schemaCoordinateMetadataMappings?.get(`${entity.name}.${fieldName}`) ?? null,
+       getFieldMetadata: createFieldMetadataGetter(supergraph, entity.name),
        // ... other methods
    }
    : null,

Also applies to: 81-83, 196-198, 215-217, 325-327, 368-370, 411-413

packages/services/api/src/modules/schema/providers/schema-manager.ts (1)

400-401: Consider adding validation for schemaMetadata.

The new schemaMetadata property is correctly added to both success and error cases, but it might benefit from runtime validation to ensure the data structure matches the expected format.

Consider adding Zod validation:

const SchemaMetadataRecord = z.record(z.string(), z.array(z.object({
  name: z.string(),
  content: z.string()
})));

// Then use it in the union type
| {
    compositeSchemaSDL: null;
    supergraphSDL: null;
    schemaCompositionErrors: Array<SchemaCompositionError>;
    tags: null;
    schemaMetadata: z.infer<typeof SchemaMetadataRecord> | null;
}

Also applies to: 407-408

packages/services/api/src/modules/schema/providers/schema-publisher.ts (1)

1320-1321: Consider adding error handling for schema metadata parsing.

The schema metadata is correctly propagated during deletion and publishing, but there's no explicit error handling for metadata parsing failures.

Consider wrapping the metadata handling in a try-catch block:

try {
  schemaMetadata: publishResult.state?.schemaMetadata ?? null,
} catch (error) {
  this.logger.error('Failed to parse schema metadata', error);
  schemaMetadata: null,
}

Also applies to: 1327-1328, 1879-1880, 1889-1890

packages/libraries/cli/examples/federation.reviews.graphql (2)

9-13: Consider adding documentation for the @meta directive.

The @meta directive is well-defined but could benefit from documentation explaining its usage and constraints.

Add documentation:

"""
Metadata directive for annotating schema elements with additional information.
@param name The identifier for the metadata entry
@param content The value associated with the metadata entry
"""
directive @meta(
  name: String!
  content: String!
) repeatable on SCHEMA | OBJECT | INTERFACE | FIELD_DEFINITION

14-19: Consider adding validation for reviewsScore.

The reviewsScore field is marked as shareable and overridden from "products", but there's no validation for the score range.

Consider adding a custom directive for validation:

directive @validateScore(min: Float!, max: Float!) on FIELD_DEFINITION

type Product implements ProductItf @key(fields: "id") {
  # ...
  reviewsScore: Float! @shareable @override(from: "products") @validateScore(min: 0.0, max: 5.0)
  # ...
}
packages/libraries/cli/examples/federation.products.graphql (2)

14-17: Consider consolidating custom directives.

The custom directives @myDirective and @hello seem to be utility directives. Consider consolidating them into a more meaningful directive structure.

Consider consolidating:

directive @customField(
  name: String!
  type: String!
  options: [String!]
) on FIELD_DEFINITION

23-33: Consider documenting the inaccessible field.

The hidden field is marked as @inaccessible but lacks documentation explaining why it's hidden and when it might be used.

Add documentation:

interface ProductItf implements SkuItf @meta(name: "domain", content: "products") {
  # ...
  """
  Hidden field used internally for product management.
  This field is marked as @inaccessible and won't be exposed in the public API.
  """
  hidden: String @inaccessible
  # ...
}
packages/services/api/package.json (1)

18-18: Dependency Category Review: Check if @graphql-hive/federation-link-utils belongs in devDependencies.
A new dependency for federation link utilities has been added as a devDependency. Given its role in supporting schema metadata extraction and federation features, please verify that it is not required at runtime. If the functionality it provides is needed in production, consider moving it to the regular dependencies section.

📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between a8a2da5 and 79db270.

⛔ Files ignored due to path filters (1)
  • pnpm-lock.yaml is excluded by !**/pnpm-lock.yaml
📒 Files selected for processing (36)
  • integration-tests/tests/api/schema/check.spec.ts (1 hunks)
  • integration-tests/tests/api/schema/publish.spec.ts (1 hunks)
  • integration-tests/tests/schema/metadata.spec.ts (1 hunks)
  • packages/libraries/cli/examples/federation.products.graphql (1 hunks)
  • packages/libraries/cli/examples/federation.reviews.graphql (1 hunks)
  • packages/libraries/federation-link-utils/src/index.ts (1 hunks)
  • packages/libraries/federation-link-utils/src/link.ts (2 hunks)
  • packages/migrations/src/actions/2025.02.14T00-00-00.schema-versions-metadata.ts (1 hunks)
  • packages/migrations/src/run-pg-migrations.ts (1 hunks)
  • packages/migrations/test/2024.07.23T09.36.00.schema-cleanup-tracker.test.ts (2 hunks)
  • packages/services/api/package.json (1 hunks)
  • packages/services/api/src/modules/schema/lib/federation-super-graph.ts (12 hunks)
  • packages/services/api/src/modules/schema/module.graphql.mappers.ts (4 hunks)
  • packages/services/api/src/modules/schema/module.graphql.ts (1 hunks)
  • packages/services/api/src/modules/schema/providers/models/composite.ts (2 hunks)
  • packages/services/api/src/modules/schema/providers/models/shared.ts (2 hunks)
  • packages/services/api/src/modules/schema/providers/models/single.ts (1 hunks)
  • packages/services/api/src/modules/schema/providers/registry-checks.ts (1 hunks)
  • packages/services/api/src/modules/schema/providers/schema-manager.ts (1 hunks)
  • packages/services/api/src/modules/schema/providers/schema-publisher.ts (3 hunks)
  • packages/services/api/src/modules/schema/resolvers/GraphQLEnumType.ts (1 hunks)
  • packages/services/api/src/modules/schema/resolvers/GraphQLField.ts (1 hunks)
  • packages/services/api/src/modules/schema/resolvers/GraphQLInterfaceType.ts (1 hunks)
  • packages/services/api/src/modules/schema/resolvers/GraphQLObjectType.ts (1 hunks)
  • packages/services/api/src/modules/schema/resolvers/SchemaExplorer.ts (7 hunks)
  • packages/services/api/src/modules/schema/resolvers/SchemaMetadata.ts (1 hunks)
  • packages/services/api/src/modules/schema/resolvers/SchemaVersion.ts (4 hunks)
  • packages/services/api/src/modules/schema/utils.ts (2 hunks)
  • packages/services/api/src/modules/shared/providers/storage.ts (2 hunks)
  • packages/services/api/src/shared/entities.ts (1 hunks)
  • packages/services/schema/src/lib/metadata-extraction.ts (1 hunks)
  • packages/services/schema/src/orchestrators.ts (12 hunks)
  • packages/services/schema/src/types.ts (1 hunks)
  • packages/services/storage/src/db/types.ts (1 hunks)
  • packages/services/storage/src/index.ts (8 hunks)
  • packages/web/app/src/components/target/explorer/super-graph-metadata.tsx (5 hunks)
✅ Files skipped from review due to trivial changes (1)
  • packages/services/api/src/modules/schema/resolvers/GraphQLEnumType.ts
🧰 Additional context used
📓 Path-based instructions (4)
`packages/services/**`: Microservices written in NodeJS and ...

packages/services/**: Microservices written in NodeJS and TypeScript. Most of the dirs under this directory are packages and deployed as Docker packages.
Interaction between services is done using tRPC.
We prefer writing code that does input/output validations with Zod.
The directories that ends with -worker are projects that are built on top of CloudFlare Workers infrastructure and deployed there.
The cdn-worker is a special one, it serves a high-available, detached CDN on CloudFlare Workers. This is how our end-users fetches the crucial data from our platform, in a way that does not depend/couple on our main API (served as GraphQL API).

  • packages/services/api/src/modules/schema/resolvers/GraphQLObjectType.ts
  • packages/services/api/package.json
  • packages/services/schema/src/types.ts
  • packages/services/storage/src/db/types.ts
  • packages/services/api/src/modules/schema/providers/models/single.ts
  • packages/services/api/src/modules/schema/resolvers/SchemaMetadata.ts
  • packages/services/api/src/modules/schema/resolvers/GraphQLField.ts
  • packages/services/api/src/modules/schema/providers/registry-checks.ts
  • packages/services/api/src/modules/schema/providers/models/shared.ts
  • packages/services/api/src/modules/schema/resolvers/GraphQLInterfaceType.ts
  • packages/services/api/src/shared/entities.ts
  • packages/services/api/src/modules/schema/providers/models/composite.ts
  • packages/services/schema/src/lib/metadata-extraction.ts
  • packages/services/api/src/modules/schema/resolvers/SchemaExplorer.ts
  • packages/services/api/src/modules/shared/providers/storage.ts
  • packages/services/api/src/modules/schema/utils.ts
  • packages/services/api/src/modules/schema/module.graphql.mappers.ts
  • packages/services/api/src/modules/schema/module.graphql.ts
  • packages/services/api/src/modules/schema/resolvers/SchemaVersion.ts
  • packages/services/api/src/modules/schema/providers/schema-publisher.ts
  • packages/services/api/src/modules/schema/lib/federation-super-graph.ts
  • packages/services/schema/src/orchestrators.ts
  • packages/services/storage/src/index.ts
  • packages/services/api/src/modules/schema/providers/schema-manager.ts
`packages/libraries/**`: Most of the dirs here are NPM packa...

packages/libraries/**: Most of the dirs here are NPM packages that provides our customers an integration library, in order to get/fetch data from Hive Console API or Hive CDN.

  • packages/libraries/federation-link-utils/src/index.ts
  • packages/libraries/federation-link-utils/src/link.ts
  • packages/libraries/cli/examples/federation.reviews.graphql
  • packages/libraries/cli/examples/federation.products.graphql
`integration-tests/**`: integration tests for ensuring featu...

integration-tests/**: integration tests for ensuring features and important flows are running. Usually, changes to the packages/ directory will affect this. In ideal flow, each PR should include integration tests.

  • integration-tests/tests/api/schema/check.spec.ts
  • integration-tests/tests/schema/metadata.spec.ts
  • integration-tests/tests/api/schema/publish.spec.ts
`packages/libraries/cli/**`: The CLI to interact with Hive C...

packages/libraries/cli/**: The CLI to interact with Hive Console APIs.

  • packages/libraries/cli/examples/federation.reviews.graphql
  • packages/libraries/cli/examples/federation.products.graphql
🪛 Biome (1.9.4)
packages/web/app/src/components/target/explorer/super-graph-metadata.tsx

[error] 26-32: Avoid using unnecessary Fragment.

A fragment is redundant if it contains only one child, or if it is the child of a html element, and is not a keyed fragment.
Unsafe fix: Remove the Fragment

(lint/complexity/noUselessFragments)

⏰ Context from checks skipped due to timeout of 90000ms (4)
  • GitHub Check: test / integration (3)
  • GitHub Check: test / integration (2)
  • GitHub Check: test / integration (1)
  • GitHub Check: test / e2e
🔇 Additional comments (37)
packages/services/schema/src/lib/metadata-extraction.ts (1)

19-20: Consider handling an undefined directive name.

The code relies on resolveImportName('https://specs.graphql-hive.com/hive', '@meta') to return a valid string. If no link directive exists or the subgraph doesn't match this spec, metaDirectiveName might be undefined. A small guard or fallback could prevent unexpected runtime errors in edge cases.

packages/services/api/src/modules/schema/lib/federation-super-graph.ts (4)

19-19: Unused metadata mapping field.

schemaCoordinateMetadataMappings is introduced but never returned or utilized in extractSuperGraphInformation. If future development will leverage this field, consider returning or referencing it now. Otherwise, remove to avoid confusion.


28-28: Returned type differs from SuperGraphInformation.

The function is changed to return only { schemaCoordinateServicesMappings }. Verify if this is intentional, especially since SuperGraphInformation includes the new schemaCoordinateMetadataMappings. If metadata mappings are needed later, consider including them in the return type to prevent future refactoring.


34-42: Dynamic resolution of join directives is clear.

Using resolveImportName instead of hardcoding '@type', '@field', etc. improves maintainability and reduces risk of naming collisions. This is a good approach.


66-67: Refactor uses consistent directive naming.

All updated references to joinDirectiveName are logical and consistent. Great job reducing reliance on string literals for directive names and ensuring uniform usage across node definitions.

Also applies to: 140-142, 153-154, 164-165, 189-190, 199-200, 213-214, 239-240, 300-303, 309-310

packages/services/schema/src/orchestrators.ts (4)

172-238: Validate potential performance impact of copying schema‑level metadata to every field.

When a subgraph implements the Hive metadata spec, the code applies schema‑level @meta directives to all fields. For very large schemas, this could be expensive. Consider verifying performance on real-world examples or caching repeated metadata objects to mitigate overhead.


281-285: Graceful handling of metadata extraction errors.

Catching metadata extraction errors and proceeding is a reasonable strategy. Logging the warning ensures visibility without breaking the main flow, which is appropriate since metadata is non-critical. This balanced approach is commendable.


431-432: Good addition of schemaMetadata to outputs.

Returning schemaMetadata alongside tags in the orchestrator’s final object ensures consumers have immediate access to aggregated metadata, aligning well with the new functionality introduced in this PR.

Also applies to: 447-448


2-2: Ensure schemaMetadata remains optional but tested.

While schemaMetadata is accurately typed as null or structured data, confirm that any downstream clients consuming this property handle both states. It may be beneficial to add a test confirming that the orchestrator gracefully returns null when no metadata is present.

Also applies to: 37-37, 167-168, 300-303

integration-tests/tests/api/schema/check.spec.ts (1)

2188-2188:

❓ Verification inconclusive

Confirm usage of the newly introduced schemaMetadata field.

Setting schemaMetadata to null here might be intentional, but ensure it is correct for all use cases of createVersion(). If this field is meant to store important metadata for referencing or debugging, consider adding corresponding test coverage to ensure the persistent storage logic works as intended.

You can run the following script to confirm whether schemaMetadata is used elsewhere in the codebase and if further adjustments are needed:


🏁 Script executed:

#!/bin/bash
# Description: Search usage and references of schemaMetadata across the repository.

ast-grep --pattern $'schemaMetadata: $_'

Length of output: 41


Attention: Please verify the usage of the schemaMetadata field

  • The integration test in integration-tests/tests/api/schema/check.spec.ts (line 2188) currently sets schemaMetadata to null.
  • An initial search using an AST grep command did not yield any references to schemaMetadata, which makes it unclear whether this field is actively used in other parts of the codebase.
  • If schemaMetadata is meant to support important metadata for functions like createVersion(), please ensure that its usage is correctly implemented and covered by appropriate tests. Manual verification of its usage (for example, by additionally searching with rg "schemaMetadata" or reviewing the implementation of createVersion()) is recommended before final approval.
packages/services/storage/src/index.ts (6)

2399-2399: Make sure schemaMetadata is handled in downstream usage.

Adding schemaMetadata strengthens the flexibility for metadata handling. Please verify that other parts of the code (like retrieval, serialization, and UI) manage this property consistently, and consider adding corresponding test coverage to confirm correct behavior.


2511-2511: Confirm consistency with other call sites.

The addition of schemaMetadata here mirrors the usage in the delete flow. Ensure all references to createVersion pass the schemaMetadata parameter correctly, and that the property is optional if not always available.


4731-4731: SchemaVersionModel enhancement looks good.

The schemaMetadata property is well integrated with nullability and typed validation, boosting clarity in the version model.


4877-4877: Parameter addition is consistent.

The new schemaMetadata parameter aligns well with the earlier property usage. This should facilitate streamlined passing of metadata to the insert logic.


4904-4905: Columns added align with existing patterns.

Adding conditional_breaking_change_metadata and schema_metadata columns extends existing schema version tracking fields and follows the established naming conventions. No issues spotted.


4924-4925: Values properly included in the SQL insert.

Referencing both the parsed conditional breaking changes and schemaMetadata accommodates the new fields without impacting core functionality, and leverages the jsonify() helper as intended.

packages/migrations/src/actions/2025.02.14T00-00-00.schema-versions-metadata.ts (1)

1-10: LGTM! Well-structured migration for adding schema metadata.

The migration is well-designed:

  • Uses JSONB type which is appropriate for flexible metadata storage
  • Includes NULL default value making it backward compatible
  • Follows the timestamp naming convention
packages/services/api/src/modules/schema/resolvers/GraphQLField.ts (1)

19-25: LGTM! Clean addition of metadata to supergraphMetadata.

The change cleanly adds metadata support while preserving existing functionality and null safety.

packages/libraries/federation-link-utils/src/index.ts (1)

44-46:

❓ Verification inconclusive

Verify if the fix for link/core directive detection is complete.

The comment suggests checking for both @link and @core directives, but the implementation only checks for the presence of links. This might still be incomplete based on the past review comment.


🏁 Script executed:

#!/bin/bash
# Search for @core directive usage in the codebase
rg -A 5 '@core' --type ts --type graphql

Length of output: 72


Review Directive Detection for Federation v2

It appears that the recent changes in packages/libraries/federation-link-utils/src/index.ts (lines 44–46) only check for the presence of @link directives by determining if linkByIdentity has any keys. However, the comment and previous discussions indicate that detection should cover both @link and @core directives.

  • The current implementation:
    • // Check if any @link or @core features are used.
      const supportsFederationV2 = Object.keys(linkByIdentity).length > 0;
    only verifies @link usage.
  • A search for @core in TypeScript files (after adjusting for the graphql file type error) returned no results. This could mean:
    • The @core usage might not exist in TS files, or
    • It is handled elsewhere, or
    • It’s an oversight if the intention was to detect both kinds.

Please manually verify whether:

  • The codebase intentionally excludes a separate check for @core directives, or
  • Additional logic is required to properly detect @core usage alongside @link.

Additionally, ensure that any simplification seen at the import name resolution on line 52 aligns with the overall feature detection strategy.

packages/services/api/src/modules/schema/resolvers/GraphQLObjectType.ts (1)

17-20: LGTM! Consistent metadata handling.

The implementation correctly adds metadata support while maintaining the existing functionality.

packages/services/api/src/modules/schema/resolvers/GraphQLInterfaceType.ts (1)

17-20: LGTM! Consistent metadata handling.

The implementation mirrors the object type resolver, ensuring consistent behavior across types.

packages/web/app/src/components/target/explorer/super-graph-metadata.tsx (1)

106-111: LGTM! Good use of memoization and tooltip configuration.

The changes effectively handle metadata display with proper performance optimization through useMemo. The tooltip delay duration of 100ms provides a good balance between responsiveness and user experience.

Also applies to: 167-167

packages/libraries/federation-link-utils/src/link.ts (1)

85-93: LGTM! Well-implemented Federation 1 support.

The implementation elegantly handles the translation from @core to @link directives, maintaining backward compatibility while following the same patterns as the existing code.

packages/migrations/test/2024.07.23T09.36.00.schema-cleanup-tracker.test.ts (1)

96-97: LGTM! Proper test coverage for schema metadata.

The test correctly verifies the schema version creation with the new schemaMetadata column while maintaining existing functionality.

Also applies to: 116-117

packages/services/api/src/modules/schema/module.graphql.mappers.ts (3)

133-133: LGTM! Added metadata support to GraphQL fields.

The metadata property is correctly typed as nullable array of SchemaMetadataMapper.


183-183: LGTM! Added field metadata retrieval support.

The getFieldMetadata method is consistently implemented across both object and interface types.

Also applies to: 197-197


300-303: LGTM! Well-structured metadata type definition.

The SchemaMetadataMapper type is properly defined with required properties for name and content.

packages/services/api/src/modules/schema/providers/models/single.ts (1)

330-330: LGTM! Added schema metadata to publish result.

The schemaMetadata is correctly derived from the composition check result with proper null handling.

packages/migrations/src/run-pg-migrations.ts (1)

161-161: LGTM!

The new schema versions metadata migration is properly added to the list, following the established patterns for naming and dynamic imports.

packages/services/api/src/modules/schema/utils.ts (1)

149-152: LGTM!

The getFieldMetadata method is well-implemented, following existing patterns and properly handling null cases.

Also applies to: 189-192

packages/services/api/src/modules/schema/resolvers/SchemaExplorer.ts (1)

63-65: LGTM!

The getFieldMetadata method is consistently implemented across all GraphQL types.

Also applies to: 81-83, 196-198, 215-217, 325-327, 368-370, 411-413

packages/services/api/src/modules/schema/providers/models/composite.ts (1)

515-515: LGTM! Metadata propagation is correctly implemented.

The changes properly propagate schema metadata from composition check results, maintaining null safety through the nullish coalescing operator.

Also applies to: 721-721

packages/services/api/src/modules/shared/providers/storage.ts (1)

416-417: LGTM! Metadata type is consistently defined.

The metadata type is consistently defined across methods with proper nullability and structure:

null | Record<string, Array<{ name: string; content: string }>>

Also applies to: 423-424, 464-465

packages/services/api/src/modules/schema/providers/registry-checks.ts (1)

303-303: LGTM! Metadata extraction is properly implemented.

The change correctly extracts schema metadata from the composition result, maintaining null safety through the nullish coalescing operator.

packages/services/api/src/modules/schema/module.graphql.ts (1)

763-763: LGTM! GraphQL schema types are well-defined.

The changes introduce a well-structured SchemaMetadata type and correctly integrate it into the SupergraphMetadata type:

  • SchemaMetadata type with non-null name and content fields.
  • metadata field in SupergraphMetadata as an array of SchemaMetadata.

Also applies to: 771-774

packages/libraries/cli/examples/federation.reviews.graphql (1)

1-8: LGTM! Well-structured federation setup with metadata.

The schema correctly uses federation directives and metadata annotations. The import statements are properly organized.

packages/libraries/cli/examples/federation.products.graphql (1)

39-54: LGTM! Well-structured Product type with comprehensive key fields.

The Product type correctly implements both interfaces and uses multiple @key directives for flexible entity resolution.

Comment on lines 108 to 113
supergraph: {
schemaCoordinateMetadataMappings: version.schemaMetadata
? new Map(Object.entries(version.schemaMetadata))
: null,
schemaCoordinateServicesMappings: supergraph?.schemaCoordinateServicesMappings ?? new Map(),
},
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

Extract duplicated metadata mapping logic.

The metadata mapping creation logic is duplicated across three methods. Consider extracting it to a shared helper function.

+  private createSupergraphMappings(version: SchemaVersion, supergraph: SupergraphInfo | null) {
+    return {
+      schemaCoordinateMetadataMappings: version.schemaMetadata
+        ? new Map(Object.entries(version.schemaMetadata))
+        : null,
+      schemaCoordinateServicesMappings: supergraph?.schemaCoordinateServicesMappings ?? new Map(),
+    };
+  }

Then use it in all three methods:

-      supergraph: {
-        schemaCoordinateMetadataMappings: version.schemaMetadata
-          ? new Map(Object.entries(version.schemaMetadata))
-          : null,
-        schemaCoordinateServicesMappings: supergraph?.schemaCoordinateServicesMappings ?? new Map(),
-      },
+      supergraph: this.createSupergraphMappings(version, supergraph),

Also applies to: 144-149, 172-177

@@ -334,6 +334,7 @@ export interface schema_versions {
previous_schema_version_id: string | null;
record_version: string | null;
schema_composition_errors: any | null;
schema_metadata: any | null;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

Consider using a more specific type for schema_metadata.

While this file is auto-generated, using any type for schema_metadata could lead to type safety issues. Consider:

  1. Defining a proper type in the database schema
  2. Using JSON schema validation for the metadata

This will help ensure data consistency and improve type safety throughout the application.

@@ -1,60 +1,74 @@
enum CURRENCY_CODE {
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I assume the prior version was for Federation 1? But I couldn't get the two example schemas working locally. I've opted to update them to Federation 2. Let me know if I should make a new version instead.

import type { SchemaBuilderApi } from '@hive/schema';
import { createTRPCProxyClient, httpLink } from '@trpc/client';

const host =
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: should this go somewhere into the test kit? 😄

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

possibly... I dont see anywhere in the codebase where we use SCHEMA_SERVICE_HOST_OVERRIDE... If anything I assume RUN_AGAINST_LOCAL_SERVICES is more practical for us to use for overriding the hosts.
I'd be in favor of removing the env var condition in this file and in the contracts.spec.ts before moving this into the test kit.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 0

🔭 Outside diff range comments (2)
integration-tests/tests/api/schema/check.spec.ts (1)

2121-2137: 🛠️ Refactor suggestion

Consider security implications of connection string handling.

The connectionString function handles sensitive database credentials. Consider:

  1. Using a secure configuration management system
  2. Adding input validation for environment variables
  3. Masking sensitive information in logs

Here's a suggested improvement:

 function connectionString() {
+  const requiredEnvVars = ['POSTGRES_USER', 'POSTGRES_PASSWORD', 'POSTGRES_HOST'];
+  for (const envVar of requiredEnvVars) {
+    if (!process.env[envVar]) {
+      throw new Error(`Missing required environment variable: ${envVar}`);
+    }
+  }
+
   const {
     POSTGRES_USER = 'postgres',
     POSTGRES_PASSWORD = 'postgres',
     POSTGRES_HOST = 'localhost',
     POSTGRES_PORT = 5432,
     POSTGRES_DB = 'registry',
     POSTGRES_SSL = null,
     POSTGRES_CONNECTION_STRING = null,
   } = process.env;
+
+  // Validate port number
+  const port = Number(POSTGRES_PORT);
+  if (isNaN(port) || port <= 0 || port > 65535) {
+    throw new Error('Invalid POSTGRES_PORT');
+  }
+
   return (
     POSTGRES_CONNECTION_STRING ||
     `postgres://${POSTGRES_USER}:${POSTGRES_PASSWORD}@${POSTGRES_HOST}:${POSTGRES_PORT}/${POSTGRES_DB}${
       POSTGRES_SSL ? '?sslmode=require' : '?sslmode=disable'
     }`
   );
 }
integration-tests/tests/api/schema/publish.spec.ts (1)

2749-2868: 🛠️ Refactor suggestion

Standardize test cleanup handling.

Make cleanup more consistent by:

  • Using beforeAll/afterAll hooks consistently
  • Ensuring all resources are properly cleaned up
  • Adding cleanup verification
  • Handling cleanup failures gracefully

Also applies to: 3991-4115

🧹 Nitpick comments (17)
packages/services/schema/src/lib/metadata-extraction.ts (1)

12-75: LGTM! The metadata extraction implementation is robust and well-structured.

The implementation:

  • Correctly resolves the metadata directive name using federation link utils
  • Properly handles field-level metadata collection
  • Includes validation for directive arguments
  • Uses schema coordinates for precise metadata mapping

A good-to-have improvement would be to add error handling for malformed metadata:

 if (metaNameArg && metaContentArg) {
+  try {
     acc.push({
       name: (metaNameArg.value as StringValueNode).value,
       content: (metaContentArg.value as StringValueNode).value,
     });
+  } catch (error) {
+    console.warn(`Failed to process metadata: ${error.message}`);
+  }
 }
integration-tests/tests/schema/metadata.spec.ts (1)

18-62: Add more test cases for metadata validation.

While the current test verifies basic metadata composition, consider adding test cases for:

  • Invalid metadata directives
  • Multiple metadata entries on the same field
  • Metadata inheritance
packages/web/app/src/components/target/explorer/super-graph-metadata.tsx (1)

19-38: Remove unnecessary Fragment wrapper.

The Fragment wrapper in the tooltip content is redundant as it contains only one child.

-        <>
           {props.supergraphMetadata.map((m, i) => (
             <div key={i}>
               <span className="font-bold">{m.name}:</span> {m.content}
             </div>
           ))}
-        </>
🧰 Tools
🪛 Biome (1.9.4)

[error] 26-32: Avoid using unnecessary Fragment.

A fragment is redundant if it contains only one child, or if it is the child of a html element, and is not a keyed fragment.
Unsafe fix: Remove the Fragment

(lint/complexity/noUselessFragments)

packages/libraries/federation-link-utils/src/link.ts (1)

85-93: Consider adding version validation for Federation 1 support.

While the implementation works, it would be beneficial to validate the Federation version to ensure the core directive is only processed for Federation 1 schemas.

 const defCores = definition.directives?.filter(({ name }) => name.value === 'core');
+const isFederation1 = links.some(link => 
+  link.identity.startsWith('https://specs.apollo.dev/federation/v1'));
 const coreLinks = defCores
+  ?.filter(() => isFederation1)
   ?.map(c => linkFromCoreArgs(c.arguments ?? []))
   .filter(l => l !== undefined);
packages/services/api/src/modules/schema/lib/federation-super-graph.ts (1)

28-28: Consider updating return type to include metadata mappings.

The return type is restricted to only include 'schemaCoordinateServicesMappings' using Pick, but the function might need to return metadata mappings in the future.

Consider updating the return type to include both mappings:

-(documentAst: DocumentNode): Pick<SuperGraphInformation, 'schemaCoordinateServicesMappings'> => {
+(documentAst: DocumentNode): Pick<SuperGraphInformation, 'schemaCoordinateServicesMappings' | 'schemaCoordinateMetadataMappings'> => {
packages/services/schema/src/orchestrators.ts (1)

281-289: Consider improving error handling for metadata extraction.

The current implementation logs a warning and proceeds when metadata extraction fails. While this is a reasonable approach since metadata isn't critical, consider providing more detailed error information.

Consider enhancing the error handling:

-logger.warn(`Cannot extract metadata from subgraphs because ${e}`);
+logger.warn({
+  error: e,
+  subgraphCount: subgraphs.length,
+  message: 'Cannot extract metadata from subgraphs',
+});
integration-tests/tests/api/schema/check.spec.ts (1)

1808-1921: Consider adding edge case tests for context ID validation.

While the tests cover basic length validation for context IDs, consider adding tests for:

  • Special characters in context IDs
  • Unicode characters
  • Leading/trailing whitespace

Here's a suggested addition:

+test.concurrent('contextId with special characters is properly handled', async ({ expect }) => {
+  const { createOrg } = await initSeed().createOwner();
+  const { createProject } = await createOrg();
+  const { createTargetAccessToken } = await createProject(ProjectType.Single);
+
+  const readToken = await createTargetAccessToken({
+    mode: 'readOnly',
+  });
+
+  const specialContextIds = [
+    'pr-123!@#$%',
+    '  pr-123  ',
+    'pr-123🚀',
+  ];
+
+  for (const contextId of specialContextIds) {
+    const checkResult = await readToken
+      .checkSchema(
+        /* GraphQL */ `
+          type Query {
+            ping: String
+          }
+        `,
+        undefined,
+        undefined,
+        contextId,
+      )
+      .then(r => r.expectNoGraphQLErrors());
+
+    expect(checkResult.schemaCheck.__typename).toBe('SchemaCheckSuccess');
+  }
+});
integration-tests/tests/api/schema/publish.spec.ts (9)

17-42: Consider adding edge cases for access control testing.

The test verifies basic access control for schema publishing, but could be enhanced with additional test cases:

  • Test with expired tokens
  • Test with tokens having partial permissions
  • Test with malformed tokens

302-333: Add metadata structure validation.

The test verifies metadata persistence but doesn't validate the structure of the metadata. Consider:

  • Adding schema validation for metadata
  • Testing with invalid metadata formats
  • Testing with large metadata payloads

291-349: Enhance CDN access token validation tests.

While the tests cover basic scenarios, consider adding:

  • Tests for token expiration
  • Tests for rate limiting
  • Tests for different HTTP methods
  • Tests for various content types

2749-2868: Improve error isolation in composition tests.

The composition error tests could be enhanced by:

  • Isolating different types of composition errors
  • Testing partial composition failures
  • Adding timeout scenarios
  • Testing recovery from composition failures

547-626: Reorganize schema change persistence tests.

The test suite could be better organized by:

  • Grouping related test cases into describe blocks
  • Using shared setup code
  • Adding test categories for different types of changes
  • Improving test case naming for better clarity

3991-4034: Expand deprecated field validation tests.

Consider adding tests for:

  • Multiple deprecated fields in the same type
  • Deprecated fields with different nullability combinations
  • Deprecated fields in interfaces and unions
  • Deprecation reason validation

3048-3110: Enhance service URL validation testing.

Add test cases for:

  • Invalid URL formats
  • URL normalization
  • URL encoding/decoding
  • URL with query parameters and fragments

370-397: Improve concurrent publishing test coverage.

Enhance race condition testing by:

  • Adding delays between operations
  • Testing with different timing patterns
  • Verifying intermediate states
  • Testing conflict resolution

527-543: Improve database connection handling.

Consider enhancing the connection handling:

  • Add connection pooling configuration
  • Add retry logic for connection failures
  • Add connection timeout handling
  • Validate connection parameters
packages/libraries/cli/examples/federation.products.graphql (1)

39-54: Product Type with Composite Keys and Metadata

The Product type strongly leverages federation through multiple @key directives for various lookup patterns—using id, a composite of sku package, and a nested key involving sku variation { id }. The application of both @meta and @tag directives on selected fields (e.g., sku and id) stylishly integrates metadata into the type definition.
Consider adding inline comments to clarify the rationale behind the multiple composite keys for future maintainers.

📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 79db270 and 69b6d47.

⛔ Files ignored due to path filters (1)
  • pnpm-lock.yaml is excluded by !**/pnpm-lock.yaml
📒 Files selected for processing (36)
  • integration-tests/tests/api/schema/check.spec.ts (1 hunks)
  • integration-tests/tests/api/schema/publish.spec.ts (1 hunks)
  • integration-tests/tests/schema/metadata.spec.ts (1 hunks)
  • packages/libraries/cli/examples/federation.products.graphql (1 hunks)
  • packages/libraries/cli/examples/federation.reviews.graphql (1 hunks)
  • packages/libraries/federation-link-utils/src/index.ts (1 hunks)
  • packages/libraries/federation-link-utils/src/link.ts (2 hunks)
  • packages/migrations/src/actions/2025.02.14T00-00-00.schema-versions-metadata.ts (1 hunks)
  • packages/migrations/src/run-pg-migrations.ts (1 hunks)
  • packages/migrations/test/2024.07.23T09.36.00.schema-cleanup-tracker.test.ts (2 hunks)
  • packages/services/api/package.json (1 hunks)
  • packages/services/api/src/modules/schema/lib/federation-super-graph.ts (12 hunks)
  • packages/services/api/src/modules/schema/module.graphql.mappers.ts (4 hunks)
  • packages/services/api/src/modules/schema/module.graphql.ts (1 hunks)
  • packages/services/api/src/modules/schema/providers/models/composite.ts (2 hunks)
  • packages/services/api/src/modules/schema/providers/models/shared.ts (2 hunks)
  • packages/services/api/src/modules/schema/providers/models/single.ts (1 hunks)
  • packages/services/api/src/modules/schema/providers/registry-checks.ts (1 hunks)
  • packages/services/api/src/modules/schema/providers/schema-manager.ts (1 hunks)
  • packages/services/api/src/modules/schema/providers/schema-publisher.ts (3 hunks)
  • packages/services/api/src/modules/schema/resolvers/GraphQLEnumType.ts (1 hunks)
  • packages/services/api/src/modules/schema/resolvers/GraphQLField.ts (1 hunks)
  • packages/services/api/src/modules/schema/resolvers/GraphQLInterfaceType.ts (1 hunks)
  • packages/services/api/src/modules/schema/resolvers/GraphQLObjectType.ts (1 hunks)
  • packages/services/api/src/modules/schema/resolvers/SchemaExplorer.ts (7 hunks)
  • packages/services/api/src/modules/schema/resolvers/SchemaMetadata.ts (1 hunks)
  • packages/services/api/src/modules/schema/resolvers/SchemaVersion.ts (4 hunks)
  • packages/services/api/src/modules/schema/utils.ts (2 hunks)
  • packages/services/api/src/modules/shared/providers/storage.ts (2 hunks)
  • packages/services/api/src/shared/entities.ts (1 hunks)
  • packages/services/schema/src/lib/metadata-extraction.ts (1 hunks)
  • packages/services/schema/src/orchestrators.ts (12 hunks)
  • packages/services/schema/src/types.ts (1 hunks)
  • packages/services/storage/src/db/types.ts (1 hunks)
  • packages/services/storage/src/index.ts (8 hunks)
  • packages/web/app/src/components/target/explorer/super-graph-metadata.tsx (5 hunks)
🚧 Files skipped from review as they are similar to previous changes (23)
  • packages/services/api/package.json
  • packages/services/api/src/modules/schema/providers/registry-checks.ts
  • packages/services/schema/src/types.ts
  • packages/services/api/src/modules/schema/resolvers/GraphQLEnumType.ts
  • packages/services/api/src/modules/schema/resolvers/GraphQLInterfaceType.ts
  • packages/services/api/src/modules/schema/resolvers/GraphQLField.ts
  • packages/services/storage/src/db/types.ts
  • packages/services/api/src/modules/schema/providers/models/single.ts
  • packages/services/api/src/shared/entities.ts
  • packages/migrations/test/2024.07.23T09.36.00.schema-cleanup-tracker.test.ts
  • packages/services/api/src/modules/schema/resolvers/GraphQLObjectType.ts
  • packages/migrations/src/actions/2025.02.14T00-00-00.schema-versions-metadata.ts
  • packages/services/api/src/modules/schema/resolvers/SchemaExplorer.ts
  • packages/libraries/federation-link-utils/src/index.ts
  • packages/services/api/src/modules/schema/resolvers/SchemaMetadata.ts
  • packages/services/api/src/modules/schema/providers/models/shared.ts
  • packages/migrations/src/run-pg-migrations.ts
  • packages/services/api/src/modules/schema/providers/models/composite.ts
  • packages/services/api/src/modules/shared/providers/storage.ts
  • packages/services/api/src/modules/schema/providers/schema-manager.ts
  • packages/services/api/src/modules/schema/utils.ts
  • packages/services/api/src/modules/schema/resolvers/SchemaVersion.ts
  • packages/services/api/src/modules/schema/providers/schema-publisher.ts
🧰 Additional context used
📓 Path-based instructions (4)
`packages/services/**`: Microservices written in NodeJS and ...

packages/services/**: Microservices written in NodeJS and TypeScript. Most of the dirs under this directory are packages and deployed as Docker packages.
Interaction between services is done using tRPC.
We prefer writing code that does input/output validations with Zod.
The directories that ends with -worker are projects that are built on top of CloudFlare Workers infrastructure and deployed there.
The cdn-worker is a special one, it serves a high-available, detached CDN on CloudFlare Workers. This is how our end-users fetches the crucial data from our platform, in a way that does not depend/couple on our main API (served as GraphQL API).

  • packages/services/api/src/modules/schema/module.graphql.mappers.ts
  • packages/services/api/src/modules/schema/module.graphql.ts
  • packages/services/api/src/modules/schema/lib/federation-super-graph.ts
  • packages/services/schema/src/orchestrators.ts
  • packages/services/schema/src/lib/metadata-extraction.ts
  • packages/services/storage/src/index.ts
`packages/libraries/**`: Most of the dirs here are NPM packa...

packages/libraries/**: Most of the dirs here are NPM packages that provides our customers an integration library, in order to get/fetch data from Hive Console API or Hive CDN.

  • packages/libraries/federation-link-utils/src/link.ts
  • packages/libraries/cli/examples/federation.products.graphql
  • packages/libraries/cli/examples/federation.reviews.graphql
`integration-tests/**`: integration tests for ensuring featu...

integration-tests/**: integration tests for ensuring features and important flows are running. Usually, changes to the packages/ directory will affect this. In ideal flow, each PR should include integration tests.

  • integration-tests/tests/schema/metadata.spec.ts
  • integration-tests/tests/api/schema/check.spec.ts
  • integration-tests/tests/api/schema/publish.spec.ts
`packages/libraries/cli/**`: The CLI to interact with Hive C...

packages/libraries/cli/**: The CLI to interact with Hive Console APIs.

  • packages/libraries/cli/examples/federation.products.graphql
  • packages/libraries/cli/examples/federation.reviews.graphql
🪛 Biome (1.9.4)
packages/web/app/src/components/target/explorer/super-graph-metadata.tsx

[error] 26-32: Avoid using unnecessary Fragment.

A fragment is redundant if it contains only one child, or if it is the child of a html element, and is not a keyed fragment.
Unsafe fix: Remove the Fragment

(lint/complexity/noUselessFragments)

⏰ Context from checks skipped due to timeout of 90000ms (10)
  • GitHub Check: alpha / npm / snapshot
  • GitHub Check: test / unit
  • GitHub Check: typescript / typecheck
  • GitHub Check: static-analysis / analyze (typescript)
  • GitHub Check: storybook-preview / deployment
  • GitHub Check: static-analysis / analyze (javascript)
  • GitHub Check: build / dockerize (linux/arm64)
  • GitHub Check: code-style / eslint-and-prettier
  • GitHub Check: build / dockerize (linux/amd64)
  • GitHub Check: alpha / cli-artifacts
🔇 Additional comments (33)
packages/services/schema/src/lib/metadata-extraction.ts (1)

77-88: LGTM! The metadata merging logic is concise and effective.

The implementation correctly handles:

  • Multiple subgraph metadata records
  • Array concatenation for duplicate coordinates
  • Undefined checks
integration-tests/tests/schema/metadata.spec.ts (1)

5-7: Consider using RUN_AGAINST_LOCAL_SERVICES instead.

As discussed in previous reviews, SCHEMA_SERVICE_HOST_OVERRIDE is not used elsewhere in the codebase. Consider using RUN_AGAINST_LOCAL_SERVICES for consistency.

packages/web/app/src/components/target/explorer/super-graph-metadata.tsx (1)

106-111: LGTM! Effective use of useMemo for metadata rendering.

The memoization prevents unnecessary re-renders of the Metadata component when only other props change.

packages/libraries/federation-link-utils/src/link.ts (1)

8-17: LGTM! Clean implementation of Federation 1 support.

The function:

  • Correctly handles feature extraction
  • Safely type casts string values
  • Maintains backward compatibility
packages/services/api/src/modules/schema/module.graphql.mappers.ts (4)

133-133: LGTM! Added metadata support to GraphQLFieldMapper.

The metadata field is correctly typed as an optional array of SchemaMetadataMapper, aligning with the PR's objective of adding metadata support.


183-183: LGTM! Added metadata retrieval to GraphQLObjectTypeMapper.

The getFieldMetadata function is correctly typed to return an optional array of SchemaMetadataMapper for a given field name.


197-197: LGTM! Added metadata retrieval to GraphQLInterfaceTypeMapper.

The getFieldMetadata function matches the implementation in GraphQLObjectTypeMapper, maintaining consistency across type definitions.


300-303: LGTM! Added SchemaMetadataMapper type.

The type definition is clean and simple, containing the essential fields for metadata:

  • name: string
  • content: string
packages/services/api/src/modules/schema/lib/federation-super-graph.ts (1)

19-19: LGTM! Added metadata mappings to SuperGraphInformation type.

The schemaCoordinateMetadataMappings field is correctly typed as a Map with string keys and metadata arrays as values, allowing for efficient metadata lookup by coordinate.

packages/services/schema/src/orchestrators.ts (1)

180-238: LGTM! Added metadata extraction from subgraphs.

The implementation correctly:

  1. Extracts metadata from subgraphs implementing the Hive metadata spec
  2. Copies metadata from schema to fields
  3. Handles metadata directives appropriately
packages/services/api/src/modules/schema/module.graphql.ts (2)

763-763: LGTM! Added metadata field to SupergraphMetadata type.

The metadata field is correctly typed as a nullable array of non-null SchemaMetadata objects.


771-774: LGTM! Added SchemaMetadata type.

The type definition is clean and matches the mapper implementation:

  • name: String! (required)
  • content: String! (required)
integration-tests/tests/api/schema/check.spec.ts (1)

764-830: LGTM! Comprehensive test coverage for schema metadata.

The test case thoroughly verifies the persistence and retrieval of schema metadata, including author and commit information. This aligns well with the PR's objective of adding metadata support.

packages/services/storage/src/index.ts (4)

711-714: LGTM! Well-structured metadata model.

The SchemaMetadataModel is well-defined using Zod for runtime validation, with a clean and flexible structure for storing metadata.


731-732: LGTM! Schema version model properly extended.

The schemaMetadata field is correctly integrated into the SchemaVersionModel with appropriate nullability and type structure.


2399-2400: LGTM! Schema version creation properly handles metadata.

The schemaMetadata field is correctly integrated into the schema version creation process, with proper JSON serialization using the jsonify helper.

Also applies to: 2511-2512


5130-5131: LGTM! SQL field selection properly updated.

The schemaMetadata field is correctly added to the SQL field selection with proper aliasing.

packages/libraries/cli/examples/federation.reviews.graphql (6)

1-8: Schema Extension and Federation Linking

The schema extension is configured to use two external link directives. The Apollo Federation link (v2.3) and the Hive-specific link with the @meta directive are clearly set up. Ensure that the imported directives (e.g., @key, @shareable, @override) remain in sync with your current federation implementation and that the URLs reflect the latest specifications.


9-13: Directive Definition for @meta

The repeatable @meta directive is defined properly with the required arguments and allowed locations. This sets a good foundation for adding metadata to schema elements.


14-20: Product Type Implementation

The Product type now implements ProductItf and is annotated with the @key directive (using id) and includes additional fields: reviewsCount, reviewsScore (with @shareable and @override(from: "products")), and reviews. The use of the override directive clearly indicates data delegation from the products service.
Ensure that downstream services consuming this type are aware of these metadata annotations.


21-27: ProductItf Interface Definition

The ProductItf interface succinctly defines the required fields. Its design supports the concrete implementation in the Product type. Confirm that any changes in the interface are propagated to all implementing types.


28-31: Query Type Update

The addition of the review(id: Int!): Review query provides a clear endpoint for fetching individual reviews. This enhances the API’s schema while remaining consistent with the overall design.


32-36: Review Type Refinement

The revised Review type now strictly contains an id and a body, which simplifies the review data structure. This aligns with the updated requirements and improves clarity.

packages/libraries/cli/examples/federation.products.graphql (10)

1-8: Enhanced Schema Extension and Directive Linking

The schema extension in this file leverages federation by importing additional directives (@key, @shareable, @inaccessible, and @tag) as well as the Hive-specific @meta. The inclusion of @meta(name: "priority", content: "tier1") at the schema level indicates an explicit priority configuration. Double-check that the inclusion of directives like @inaccessible and @tag meets your current data exposure requirements.


9-13: Consistent Definition of @meta Directive

The @meta directive is defined consistently here as in the reviews schema. This reuse enhances uniform metadata annotation across your GraphQL schemas.


14-17: Additional Directives (@myDirective & @hello) Definition

Both @myDirective (with a parameter) and the parameterless @hello directive are properly defined for FIELD_DEFINITION. Their definitions appear straightforward; ensure that their usage is documented so that schema consumers understand their roles.


18-22: Query Type Enhancements

The Query type now exposes two endpoints:

  • allProducts, which is enhanced with a @meta annotation specifying the owner as "hive-team", and
  • product(id: ID!): ProductItf.

These additions provide more ways to query product data while leveraging the new metadata mechanisms.


23-33: ProductItf Interface with Implemented Inheritance

The ProductItf interface now implements SkuItf and is decorated with @meta(name: "domain", content: "products"). The interface’s field definitions—including hidden tagged with @inaccessible and oldField marked as deprecated—are clear.
It would be beneficial to ensure that client implementations stop relying on the deprecated oldField promptly.


35-37: SkuItf Interface Declaration

The SkuItf interface is minimal and focused, containing the singular sku field. This promotes reusability when combined with ProductItf.


56-59: Enum Declaration: ShippingClass

The ShippingClass enum is concise, offering two clear shipping options: STANDARD and EXPRESS. This simple declaration is appropriate and complete.


61-65: ProductVariation Type Definition

The ProductVariation type, with its id and an optional name, is defined in a straightforward manner. This aligns well with the interface requirements.


66-70: ProductDimension Type with Shareable Directive

The ProductDimension type is marked with the @shareable directive, allowing it to be reused across services if needed. The fields size and weight are clear and well-defined.


71-75: User Type with Key and Shareable Field

The User type is annotated with a @key that uses the email field and includes a shareable field, totalProductsCreated. This ensures that user data can be federated efficiently.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Let's have some tests for this implementation.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Covered via the integration test. I'm happy to add unit tests too if preferred.

Comment on lines 285 to 288
} catch (e: unknown) {
// warn and proceed because metadata isn't considered critical
logger.warn(`Cannot extract metadata from subgraphs because ${e}`);
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What could be thrown there?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is a remnant of a previous iteration. Ive removed the try/catch. It shouldn't throw.

Comment on lines +351 to 353
// @note Although it can differ from the supergraph's, ignore metadata on contracts.
...subgraph,
typeDefs: filteredSubgraph.typeDefs,
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What is the reason here for ignoring the metadata?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Metadata will only differ between contracts and supergraph for @shared fields or entities, where one subgraph could impact another.
I think this is an edge case and the additional complexity of calculating and displaying metadata doesn't provide much benefit to the users.

@n1ru4l
Copy link
Contributor

n1ru4l commented Feb 19, 2025

Could there be a use case where people want to specify meta-data but not have it appear within the supergraph?

Copy link
Contributor

@n1ru4l n1ru4l left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Overall, I think the implementation looks solid to me. ✅

I would like some more unit or integration tests around the metadata extraction to have a bit more confidence and make sure we are not breaking something unexpectedly.

I want @kamilkisiela to review this as well before merging things, to double check this covers what our clients need.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 0

🧹 Nitpick comments (3)
integration-tests/tests/schema/metadata.spec.ts (3)

5-7: Consider removing environment variable override.

Based on the previous discussion in the PR, consider removing the SCHEMA_SERVICE_HOST_OVERRIDE environment variable and relying solely on getServiceHost from the test kit, as it's more consistently used across the codebase.

-const host =
-  process.env['SCHEMA_SERVICE_HOST_OVERRIDE'] ||
-  (await getServiceHost('schema', 3002).then(r => `http://${r}`));
+const host = await getServiceHost('schema', 3002).then(r => `http://${r}`);

73-73: Improve test description clarity.

The current test description "@meta does not need to be in supergraph" could be more specific about what aspect of the metadata is being verified.

-  test('@meta does not need to be in supergraph', () => {
+  test('supergraph should preserve schema structure without including @meta directive content', () => {

167-213: Consider adding error scenario test cases.

While the happy path is well tested, consider adding test cases for error scenarios such as:

  • Invalid metadata content
  • Conflicting metadata definitions
  • Missing required metadata fields

Would you like me to help generate additional test cases for these scenarios?

📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 69b6d47 and f6bcd9b.

⛔ Files ignored due to path filters (1)
  • pnpm-lock.yaml is excluded by !**/pnpm-lock.yaml
📒 Files selected for processing (2)
  • integration-tests/tests/schema/metadata.spec.ts (1 hunks)
  • packages/migrations/test/2024.07.23T09.36.00.schema-cleanup-tracker.test.ts (2 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
  • packages/migrations/test/2024.07.23T09.36.00.schema-cleanup-tracker.test.ts
🧰 Additional context used
📓 Path-based instructions (1)
`integration-tests/**`: integration tests for ensuring featu...

integration-tests/**: integration tests for ensuring features and important flows are running. Usually, changes to the packages/ directory will affect this. In ideal flow, each PR should include integration tests.

  • integration-tests/tests/schema/metadata.spec.ts
⏰ Context from checks skipped due to timeout of 90000ms (11)
  • GitHub Check: alpha / npm / snapshot
  • GitHub Check: test / unit
  • GitHub Check: static-analysis / analyze (typescript)
  • GitHub Check: typescript / typecheck
  • GitHub Check: storybook-preview / deployment
  • GitHub Check: static-analysis / analyze (javascript)
  • GitHub Check: code-style / eslint-and-prettier
  • GitHub Check: build / dockerize (linux/arm64)
  • GitHub Check: build / dockerize (linux/amd64)
  • GitHub Check: alpha / cli-artifacts
  • GitHub Check: Mend Security Check
🔇 Additional comments (1)
integration-tests/tests/schema/metadata.spec.ts (1)

26-28: Ensure consistent federation versions across schemas.

The schemas are using different federation versions (v2.3 vs v2.1). While this might work, it's better to maintain consistency to prevent potential compatibility issues.

Consider using the same federation version in both schemas:

-            @link(url: "https://specs.apollo.dev/federation/v2.1")
+            @link(url: "https://specs.apollo.dev/federation/v2.3")

Also applies to: 51-53

@jdolle
Copy link
Collaborator Author

jdolle commented Feb 19, 2025

Could there be a use case where people want to specify meta-data but not have it appear within the supergraph?

@composeDirective was required in the first iteration, but isn't anymore. Sorry, I forgot to update the test.

I've updated the tests to cover more cases and removed the @composeDirective to clarify that it isnt required.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 0

🧹 Nitpick comments (1)
packages/services/schema/src/orchestrators.ts (1)

172-238: Consider extracting nested mapping functions for better maintainability.

The metadata application logic is well-implemented but could be more maintainable.

Consider these improvements:

+const HIVE_METADATA_SPEC = 'https://specs.graphql-hive.com/hive';
+const HIVE_METADATA_VERSION = 'v1.0';
+
+function applyMetadataToSubgraph(subgraph: { typeDefs: DocumentNode; name: string; url?: string }) {
+  const { matchesImplementation, resolveImportName } = extractLinkImplementations(subgraph.typeDefs);
+  if (!matchesImplementation(HIVE_METADATA_SPEC, HIVE_METADATA_VERSION)) {
+    return subgraph;
+  }
+  
+  const metaDirectiveName = resolveImportName(HIVE_METADATA_SPEC, '@meta');
+  return {
+    ...subgraph,
+    typeDefs: applyMetadataToFields(subgraph.typeDefs, metaDirectiveName),
+  };
+}

 const subgraphs = schemas
   .map(schema => ({
     typeDefs: trimDescriptions(parse(schema.raw)),
     name: schema.source,
     url: 'url' in schema && typeof schema.url === 'string' ? schema.url : undefined,
   }))
-  .map(subgraph => {
-    const { matchesImplementation, resolveImportName } = extractLinkImplementations(
-      subgraph.typeDefs,
-    );
-    if (matchesImplementation('https://specs.graphql-hive.com/hive', 'v1.0')) {
-      // ... rest of the implementation
-    }
-    return subgraph;
-  });
+  .map(applyMetadataToSubgraph);
📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between f6bcd9b and 271ebc2.

📒 Files selected for processing (2)
  • packages/libraries/federation-link-utils/src/link.ts (2 hunks)
  • packages/services/schema/src/orchestrators.ts (12 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
  • packages/libraries/federation-link-utils/src/link.ts
🧰 Additional context used
📓 Path-based instructions (1)
`packages/services/**`: Microservices written in NodeJS and ...

packages/services/**: Microservices written in NodeJS and TypeScript. Most of the dirs under this directory are packages and deployed as Docker packages.
Interaction between services is done using tRPC.
We prefer writing code that does input/output validations with Zod.
The directories that ends with -worker are projects that are built on top of CloudFlare Workers infrastructure and deployed there.
The cdn-worker is a special one, it serves a high-available, detached CDN on CloudFlare Workers. This is how our end-users fetches the crucial data from our platform, in a way that does not depend/couple on our main API (served as GraphQL API).

  • packages/services/schema/src/orchestrators.ts
⏰ Context from checks skipped due to timeout of 90000ms (11)
  • GitHub Check: alpha / npm / snapshot
  • GitHub Check: test / unit
  • GitHub Check: typescript / typecheck
  • GitHub Check: storybook-preview / deployment
  • GitHub Check: static-analysis / analyze (typescript)
  • GitHub Check: static-analysis / analyze (javascript)
  • GitHub Check: code-style / eslint-and-prettier
  • GitHub Check: test / db-migrations
  • GitHub Check: build / dockerize (linux/arm64)
  • GitHub Check: build / dockerize (linux/amd64)
  • GitHub Check: alpha / cli-artifacts
🔇 Additional comments (4)
packages/services/schema/src/orchestrators.ts (4)

2-2: LGTM! Well-structured type imports and definitions.

The new imports and type definitions are properly organized to support the metadata functionality. The schemaMetadata type is correctly defined as a nullable record mapping strings to arrays of metadata objects.

Also applies to: 37-37, 167-167


281-282: LGTM! Proper metadata merging implementation.

The metadata extraction and merging from subgraphs is implemented correctly using the imported utilities.


345-347: Verify the impact of ignoring metadata on contracts.

The code ignores metadata on contracts as noted in the comment. Based on the PR comments, this is intentional as metadata will only differ between contracts and supergraph for @shared fields or entities.

Please confirm if there are any edge cases where contract-specific metadata would be valuable for users.


427-442: LGTM! Comprehensive error handling.

The error handling is thorough and consistently sets schemaMetadata to null in error cases, including cache timeout errors.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 0

🧹 Nitpick comments (3)
integration-tests/tests/schema/metadata.spec.ts (3)

18-18: Enhance test suite description.

The current description is too generic. Consider making it more specific to better reflect what aspects of metadata processing are being tested.

-describe('schema service can process metadata', async () => {
+describe('schema service - metadata processing and federation composition', async () => {

73-165: Consider enhancing supergraph validation test.

While snapshot testing is useful, consider adding specific assertions for critical aspects of the supergraph composition. This would make the test more maintainable and failures more obvious.

test('validates supergraph composition and @meta directive handling', () => {
  // Keep snapshot for full validation
  expect(result.supergraph).toMatchInlineSnapshot(`...`);
  
  // Add specific assertions
  expect(result.supergraph).toContain('@link(url: "https://specs.graphql-hive.com/hive/v1.0"');
  expect(result.supergraph).not.toContain('directive @meta');
});

167-212: Enhance metadata validation test with specific assertions.

While the snapshot test is comprehensive, consider adding specific assertions to validate critical metadata relationships and inheritance. This would make the test more resilient to changes and failures more descriptive.

test('validates metadata inheritance and composition from schema, type, and field levels', () => {
  // Keep snapshot for full validation
  expect(result.schemaMetadata).toMatchInlineSnapshot(`...`);
  
  // Add specific assertions
  const userNameMetadata = result.schemaMetadata['User.name'];
  expect(userNameMetadata).toHaveLength(3); // Field, type, and schema level
  expect(userNameMetadata).toContainEqual({
    name: 'field',
    content: 'User.name'
  });
  expect(userNameMetadata).toContainEqual({
    name: 'type',
    content: 'user'
  });
});
📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 271ebc2 and 2f6bd1e.

📒 Files selected for processing (1)
  • integration-tests/tests/schema/metadata.spec.ts (1 hunks)
🧰 Additional context used
📓 Path-based instructions (1)
`integration-tests/**`: integration tests for ensuring featu...

integration-tests/**: integration tests for ensuring features and important flows are running. Usually, changes to the packages/ directory will affect this. In ideal flow, each PR should include integration tests.

  • integration-tests/tests/schema/metadata.spec.ts
⏰ Context from checks skipped due to timeout of 90000ms (10)
  • GitHub Check: alpha / npm / snapshot
  • GitHub Check: test / unit
  • GitHub Check: static-analysis / analyze (typescript)
  • GitHub Check: typescript / typecheck
  • GitHub Check: static-analysis / analyze (javascript)
  • GitHub Check: storybook-preview / deployment
  • GitHub Check: build / dockerize (linux/arm64)
  • GitHub Check: build / dockerize (linux/amd64)
  • GitHub Check: code-style / eslint-and-prettier
  • GitHub Check: alpha / cli-artifacts
🔇 Additional comments (1)
integration-tests/tests/schema/metadata.spec.ts (1)

5-7: Consider standardizing service host configuration.

Based on the previous discussion, RUN_AGAINST_LOCAL_SERVICES might be more practical than SCHEMA_SERVICE_HOST_OVERRIDE. Consider removing the environment variable condition and using the standard approach for consistency.

-const host =
-  process.env['SCHEMA_SERVICE_HOST_OVERRIDE'] ||
-  (await getServiceHost('schema', 3002).then(r => `http://${r}`));
+const host = await getServiceHost('schema', 3002).then(r => `http://${r}`);

@kamilkisiela
Copy link
Contributor

I will review the code tomorrow, but in terms of UI, I think we could pass the metadata to the subgraph badge.
Metadata is bound to a subgraph anyway, and when hovering on the subgraph, we could show extra information (like contact details).

If at least two subgraphs share a field and those team add metadata (like slack channel or just the name of the team), a dedicated icon for metadata will fall short. It won't be able to display info about two teams. If we merge it into the subgraph badge, it connects nicely.

@jdolle
Copy link
Collaborator Author

jdolle commented Feb 19, 2025

That's an interesting point. I like the idea of linking it to the subgraph, and I agree that fewer icons the better.
I'll make that adjustment.

@jdolle
Copy link
Collaborator Author

jdolle commented Feb 20, 2025

Screenshot 2025-02-19 at 4 23 33 PM I've updated the API to add an additional "source" field to indicate the origin of the metadata. I felt "source" was more accurate than "subgraph" if ever this metadata can be generated in another way.

And I wanted to still indicate which subgraphs were contributing metadata so people dont need to hover over every single one. So I added an asterisk for if a subgraph is contributing metadata.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🧹 Nitpick comments (4)
packages/services/schema/src/orchestrators.ts (1)

173-239: Evaluate metadata injection logic in subgraphs.
The block introduces logic to:

  • Identify subgraphs implementing the Hive metadata spec.
  • Copy schema, type, or interface-level directives to their field nodes for easier downstream usage.

Potential edge case: Input object fields or union types are not addressed here. If metadata is needed on those, you may need additional handling. Otherwise, the approach to replicating metadata at the field level is well-organized for composition.

packages/services/schema/src/lib/metadata-extraction.ts (1)

17-80: Implementing extractMetadata function.
The logic systematically:

  1. Resolves the @meta directive name.
  2. Visits object and interface fields to collect metadata.
  3. Associates the metadata with each field’s coordinate.

Consider tests for nested or edge cases (e.g., hidden or non-standard fields). The existing integration tests might suffice, but dedicated unit tests can improve clarity and maintainability.

packages/web/app/src/components/target/explorer/super-graph-metadata.tsx (2)

24-36: Simplify fragment usage and improve accessibility.

The tooltip implementation can be improved:

  1. The fragment wrapping the metadata map is unnecessary.
  2. Consider adding aria-label for better accessibility.

Apply this diff:

-      content={
-        <>
-          {props.supergraphMetadata.map((m, i) => (
-            <div key={i}>
-              <span className="font-bold">{m.name}:</span> {m.content}
-            </div>
-          ))}
-        </>
-      }
+      content={props.supergraphMetadata.map((m, i) => (
+        <div key={i}>
+          <span className="font-bold">{m.name}:</span> {m.content}
+        </div>
+      ))}
+      aria-label="Metadata information"
🧰 Tools
🪛 Biome (1.9.4)

[error] 26-32: Avoid using unnecessary Fragment.

A fragment is redundant if it contains only one child, or if it is the child of a html element, and is not a keyed fragment.
Unsafe fix: Remove the Fragment

(lint/complexity/noUselessFragments)


194-194: Consider adjusting tooltip delay duration.

The 100ms delay might be too short for a good user experience. Consider increasing it to allow users to intentionally hover over elements.

-    <TooltipProvider delayDuration={100}>
+    <TooltipProvider delayDuration={300}>
📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 2f6bd1e and e980c6d.

📒 Files selected for processing (14)
  • integration-tests/tests/schema/metadata.spec.ts (1 hunks)
  • packages/services/api/src/modules/schema/lib/federation-super-graph.ts (12 hunks)
  • packages/services/api/src/modules/schema/module.graphql.mappers.ts (4 hunks)
  • packages/services/api/src/modules/schema/module.graphql.ts (1 hunks)
  • packages/services/api/src/modules/schema/providers/schema-manager.ts (1 hunks)
  • packages/services/api/src/modules/schema/resolvers/GraphQLField.ts (1 hunks)
  • packages/services/api/src/modules/schema/resolvers/GraphQLObjectType.ts (1 hunks)
  • packages/services/api/src/modules/schema/resolvers/SchemaVersion.ts (4 hunks)
  • packages/services/api/src/modules/shared/providers/storage.ts (2 hunks)
  • packages/services/schema/src/lib/metadata-extraction.ts (1 hunks)
  • packages/services/schema/src/orchestrators.ts (12 hunks)
  • packages/services/schema/src/types.ts (2 hunks)
  • packages/services/storage/src/index.ts (8 hunks)
  • packages/web/app/src/components/target/explorer/super-graph-metadata.tsx (6 hunks)
🚧 Files skipped from review as they are similar to previous changes (6)
  • packages/services/api/src/modules/schema/resolvers/GraphQLObjectType.ts
  • packages/services/api/src/modules/schema/resolvers/GraphQLField.ts
  • integration-tests/tests/schema/metadata.spec.ts
  • packages/services/api/src/modules/schema/providers/schema-manager.ts
  • packages/services/api/src/modules/schema/resolvers/SchemaVersion.ts
  • packages/services/api/src/modules/shared/providers/storage.ts
🧰 Additional context used
📓 Path-based instructions (1)
`packages/services/**`: Microservices written in NodeJS and ...

packages/services/**: Microservices written in NodeJS and TypeScript. Most of the dirs under this directory are packages and deployed as Docker packages.
Interaction between services is done using tRPC.
We prefer writing code that does input/output validations with Zod.
The directories that ends with -worker are projects that are built on top of CloudFlare Workers infrastructure and deployed there.
The cdn-worker is a special one, it serves a high-available, detached CDN on CloudFlare Workers. This is how our end-users fetches the crucial data from our platform, in a way that does not depend/couple on our main API (served as GraphQL API).

  • packages/services/schema/src/types.ts
  • packages/services/schema/src/lib/metadata-extraction.ts
  • packages/services/api/src/modules/schema/module.graphql.mappers.ts
  • packages/services/api/src/modules/schema/module.graphql.ts
  • packages/services/api/src/modules/schema/lib/federation-super-graph.ts
  • packages/services/schema/src/orchestrators.ts
  • packages/services/storage/src/index.ts
🪛 Biome (1.9.4)
packages/web/app/src/components/target/explorer/super-graph-metadata.tsx

[error] 26-32: Avoid using unnecessary Fragment.

A fragment is redundant if it contains only one child, or if it is the child of a html element, and is not a keyed fragment.
Unsafe fix: Remove the Fragment

(lint/complexity/noUselessFragments)


[error] 78-78: Missing key property for this element in iterable.

The order of the items may change, and having a key can help React identify which item was moved.
Check the React documentation.

(lint/correctness/useJsxKeyInIterable)


[error] 79-79: Missing key property for this element in iterable.

The order of the items may change, and having a key can help React identify which item was moved.
Check the React documentation.

(lint/correctness/useJsxKeyInIterable)

⏰ Context from checks skipped due to timeout of 90000ms (2)
  • GitHub Check: build / dockerize (linux/arm64)
  • GitHub Check: build / dockerize (linux/amd64)
🔇 Additional comments (26)
packages/services/storage/src/index.ts (4)

711-715: LGTM! Well-structured schema metadata model.

The SchemaMetadataModel is well-defined with appropriate field types and constraints:

  • name: Required string for metadata identifier
  • content: Required string for metadata content
  • source: Optional string with null default for metadata origin

732-732: LGTM! Proper integration of schema metadata into version model.

The schemaMetadata field is correctly integrated into SchemaVersionModel with appropriate nullability and type structure.


878-878: LGTM! Proper handling of schema metadata in version creation.

The insertSchemaVersion function correctly:

  1. Accepts schema metadata as an optional parameter
  2. Properly serializes it to JSON for storage
  3. Maintains type safety through the model

Also applies to: 925-926


5132-5132: LGTM! SQL field mapping for schema metadata.

The schema version SQL fields correctly include the schema_metadata column with proper field aliasing.

packages/services/schema/src/orchestrators.ts (12)

2-2: No concerns with new type imports.
The added imports from graphql are used consistently within the file.


37-37: Importing metadata extraction utilities.
These imports seem correct and well-scoped to the new metadata extraction logic.


42-42: Importing newly introduced Metadata type.
This ensures strong typing of metadata objects throughout the orchestrator.


168-168: Added schemaMetadata to cached result.
The addition of schemaMetadata is consistent with the new metadata approach, allowing the orchestration cache to store structured metadata.


282-285: Extracting metadata for each subgraph.
This extraction step looks correct. It might be worth verifying if subgraphs without any @meta directives gracefully return empty mappings.


294-294: Merging metadata using Object.fromEntries().
This effectively converts the merged metadata Map to a serializable structure. No issues found.


300-301: Default assignment of schemaMetadata: null in failure scenarios.
This cleanly indicates that no metadata is available.


347-347: Comment clarifying metadata is ignored for contracts.
This explicit note helps maintainers understand the decision to avoid copying metadata to contract-based compositions.


349-349: Consistent approach for contract typeDefs.
No issues in continuing to rely on the compositional logic without including metadata.


388-388: Resetting schemaMetadata to null on network errors.
This is consistent with marking it unavailable when an error occurs.


427-427: Surface schemaMetadata in the orchestrator output.
This ensures that downstream processes can consume the entire metadata.


444-444: Fallback to null in error handling.
Providing null remains consistent with other early bail-out scenarios.

packages/services/schema/src/types.ts (2)

19-20: Introducing schemaMetadata in output.
Using a Record is a convenient approach for serialized data; it keeps the structure simple when returning from orchestrations.


41-48: Definition of the Metadata type.
This structured type cleanly captures name, content, and source, aligning with the new subgraph-level metadata system.

packages/services/schema/src/lib/metadata-extraction.ts (4)

1-9: Imports for handling directives and node visitors.
These imports support the metadata extraction logic. No problems identified.


10-10: Link utilities import.
Importing extractLinkImplementations is essential for resolving custom directive import names.


12-16: New SubgraphMetadata type.
Storing an array of metadata objects per coordinate is a clear approach.


82-93: Merging metadata across subgraphs.
The function merges entries by schema coordinate. Consider verifying that duplicate or conflicting entries merge as intended (e.g., same coordinate, different content).

packages/services/api/src/modules/schema/module.graphql.ts (1)

771-784: Well-structured schema definition for metadata!

The SchemaMetadata type is well-defined with clear field descriptions and appropriate nullability constraints. The structure allows for flexible metadata storage with source tracking.

packages/services/api/src/modules/schema/module.graphql.mappers.ts (1)

300-304: Clean and type-safe mapper definition!

The SchemaMetadataMapper type correctly mirrors the GraphQL schema definition, ensuring type safety throughout the application.

packages/services/api/src/modules/schema/lib/federation-super-graph.ts (2)

16-24: Well-structured metadata mapping in SuperGraphInformation!

The metadata mapping structure is well-designed, using a Map for efficient lookups and maintaining consistency with the schema definition.


38-47: Improved maintainability with directive name resolution!

Good refactoring to use extractLinkImplementations for resolving directive names instead of hardcoding them. This makes the code more maintainable and less prone to errors.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 0

🧹 Nitpick comments (1)
packages/services/api/src/modules/schema/lib/federation-super-graph.ts (1)

16-24: Consider implementing metadata population.

While the type system is prepared for metadata with schemaCoordinateMetadataMappings, the implementation doesn't populate this data. Consider:

  1. Extending extractSuperGraphInformation to return both mappings
  2. Implementing metadata extraction logic similar to service mappings
  3. Adding tests for metadata extraction

This would complete the metadata support outlined in the PR objectives.

Also applies to: 32-32

📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between e980c6d and 604e5d4.

📒 Files selected for processing (8)
  • packages/services/api/src/modules/schema/lib/federation-super-graph.ts (12 hunks)
  • packages/services/api/src/modules/schema/module.graphql.mappers.ts (4 hunks)
  • packages/services/api/src/modules/schema/providers/models/shared.ts (2 hunks)
  • packages/services/api/src/modules/schema/providers/schema-manager.ts (1 hunks)
  • packages/services/api/src/modules/schema/resolvers/GraphQLInterfaceType.ts (1 hunks)
  • packages/services/api/src/modules/shared/providers/storage.ts (2 hunks)
  • packages/services/api/src/shared/entities.ts (1 hunks)
  • packages/services/storage/src/index.ts (8 hunks)
🚧 Files skipped from review as they are similar to previous changes (5)
  • packages/services/api/src/modules/schema/resolvers/GraphQLInterfaceType.ts
  • packages/services/api/src/modules/schema/providers/models/shared.ts
  • packages/services/api/src/modules/schema/providers/schema-manager.ts
  • packages/services/api/src/modules/shared/providers/storage.ts
  • packages/services/api/src/shared/entities.ts
🧰 Additional context used
📓 Path-based instructions (1)
`packages/services/**`: Microservices written in NodeJS and ...

packages/services/**: Microservices written in NodeJS and TypeScript. Most of the dirs under this directory are packages and deployed as Docker packages.
Interaction between services is done using tRPC.
We prefer writing code that does input/output validations with Zod.
The directories that ends with -worker are projects that are built on top of CloudFlare Workers infrastructure and deployed there.
The cdn-worker is a special one, it serves a high-available, detached CDN on CloudFlare Workers. This is how our end-users fetches the crucial data from our platform, in a way that does not depend/couple on our main API (served as GraphQL API).

  • packages/services/storage/src/index.ts
  • packages/services/api/src/modules/schema/module.graphql.mappers.ts
  • packages/services/api/src/modules/schema/lib/federation-super-graph.ts
⏰ Context from checks skipped due to timeout of 90000ms (10)
  • GitHub Check: alpha / npm / snapshot
  • GitHub Check: test / unit
  • GitHub Check: typescript / typecheck
  • GitHub Check: static-analysis / analyze (typescript)
  • GitHub Check: storybook-preview / deployment
  • GitHub Check: static-analysis / analyze (javascript)
  • GitHub Check: build / dockerize (linux/arm64)
  • GitHub Check: build / dockerize (linux/amd64)
  • GitHub Check: code-style / eslint-and-prettier
  • GitHub Check: alpha / cli-artifacts
🔇 Additional comments (13)
packages/services/storage/src/index.ts (4)

711-715: LGTM! Well-structured schema metadata model.

The SchemaMetadataModel is well-defined with appropriate types and constraints:

  • name and content are required strings
  • source is an optional nullable string with a default value of null

732-732: LGTM! Proper integration with SchemaVersionModel.

The schemaMetadata field is correctly integrated into the SchemaVersionModel with appropriate typing as a nullable record mapping strings to arrays of SchemaMetadataModel.


878-881: LGTM! Type-safe argument handling in insertSchemaVersion.

The schemaMetadata argument is properly typed in the insertSchemaVersion function parameters, maintaining type safety and consistency with the model definition.


2399-2399: LGTM! Proper SQL integration for schema metadata.

The schema metadata is correctly handled in SQL operations:

  • Added to the INSERT statement columns
  • Properly jsonified using the helper function
  • Included in the RETURNING clause

Also applies to: 2408-2409, 2928-2929

packages/services/api/src/modules/schema/module.graphql.mappers.ts (4)

133-133: LGTM! Well-structured metadata property.

The schemaMetadata property is correctly typed as a nullable array of metadata entries, allowing fields to have multiple metadata items or none.


183-183: LGTM! Well-defined metadata accessor.

The getFieldMetadata method provides a clean interface for retrieving field-specific metadata in object types.


197-197: LGTM! Consistent metadata access across types.

The getFieldMetadata method maintains a consistent interface between object and interface types.


300-304: LGTM! Well-structured metadata type.

The SchemaMetadataMapper type effectively captures metadata with:

  • A descriptive name
  • Content for the metadata value
  • Optional source field to track metadata origin

This aligns well with the PR's goal of supporting metadata visualization and the feedback about tracking metadata sources.

packages/services/api/src/modules/schema/lib/federation-super-graph.ts (5)

19-23: LGTM! Well-structured metadata type definition.

The new schemaCoordinateMetadataMappings type is well-defined and aligns with the PR's objective of supporting metadata visualization in the Hive UI.


38-46: LGTM! Improved directive resolution.

The use of extractLinkImplementations to dynamically resolve directive names is a more maintainable approach than hardcoding them.


48-56: LGTM! Well-implemented helper function.

The getJoinGraphEnumServiceName function properly handles edge cases and uses the dynamically resolved directive name.


88-88: LGTM! Consistent directive name updates.

The hardcoded directive names have been consistently replaced with dynamically resolved ones throughout the code, improving maintainability while preserving the core federation logic.

Also applies to: 157-157, 168-168, 193-193, 203-203, 217-217, 243-243


32-32:

❓ Verification inconclusive

Verify the return type change impact.

The function now only returns schemaCoordinateServicesMappings despite SuperGraphInformation type including schemaCoordinateMetadataMappings. This might indicate missing metadata population logic.


🏁 Script executed:

#!/bin/bash
# Search for any other files that might be populating the metadata mappings
rg -l "schemaCoordinateMetadataMappings" --type ts

Length of output: 364


Clarify Metadata Population in SuperGraphInformation

The function now returns only schemaCoordinateServicesMappings while the complete SuperGraphInformation type also includes schemaCoordinateMetadataMappings. Please verify whether this change is intentional—that is, if metadata mappings are now populated elsewhere (e.g., via logic in files like packages/services/schema/src/lib/metadata-extraction.ts or packages/services/api/src/modules/schema/utils.ts)—or if additional logic is needed here to extract and include them. If this omission is deliberate, consider documenting the rationale to avoid confusion.

…tadata.tsx

Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com>
Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 0

🧹 Nitpick comments (3)
packages/web/app/src/components/target/explorer/super-graph-metadata.tsx (3)

19-38: Define proper TypeScript types for metadata.

Consider extracting the metadata type to improve type safety and reusability across components.

+type MetadataItem = {
+  name: string;
+  content: string;
+};
+
-function Metadata(props: { supergraphMetadata: Array<{ name: string; content: string }> }) {
+function Metadata(props: { supergraphMetadata: Array<MetadataItem> }) {

Also, the fragment wrapper can be simplified:

-        <>
           {props.supergraphMetadata.map((m, i) => (
             <div key={i}>
               <span className="font-bold">{m.name}:</span> {m.content}
             </div>
           ))}
-        </>
🧰 Tools
🪛 Biome (1.9.4)

[error] 26-32: Avoid using unnecessary Fragment.

A fragment is redundant if it contains only one child, or if it is the child of a html element, and is not a keyed fragment.
Unsafe fix: Remove the Fragment

(lint/complexity/noUselessFragments)


128-185: Reduce code duplication in service name mapping.

The mapping logic for service names is duplicated. Consider extracting it into a reusable function.

+  const renderSubgraphChip = (serviceName: string, index: number, showTooltip: boolean) => {
+    const meta = supergraphMetadata.metadata?.filter(({ source }) => source === serviceName);
+    return (
+      <SubgraphChip
+        organizationSlug={props.organizationSlug}
+        projectSlug={props.projectSlug}
+        targetSlug={props.targetSlug}
+        key={`${serviceName}-${index}`}
+        text={serviceName}
+        tooltip={showTooltip}
+        metadata={meta}
+      />
+    );
+  };

   const items = useMemo(() => {
     if (supergraphMetadata.ownedByServiceNames == null) {
       return null;
     }

     if (supergraphMetadata.ownedByServiceNames.length <= previewThreshold) {
       return [
-        supergraphMetadata.ownedByServiceNames.map((serviceName, index) => {
-          const meta = supergraphMetadata.metadata?.filter(({ source }) => source === serviceName);
-          return (
-            <SubgraphChip
-              organizationSlug={props.organizationSlug}
-              projectSlug={props.projectSlug}
-              targetSlug={props.targetSlug}
-              key={`${serviceName}-${index}`}
-              text={serviceName}
-              tooltip
-              metadata={meta}
-            />
-          );
-        }),
+        supergraphMetadata.ownedByServiceNames.map((serviceName, index) => 
+          renderSubgraphChip(serviceName, index, true)
+        ),
         null,
       ] as const;
     }

     return [
       supergraphMetadata.ownedByServiceNames
         .slice(0, previewThreshold)
-        .map((serviceName, index) => {
-          const meta = supergraphMetadata.metadata?.filter(({ source }) => source === serviceName);
-          return (
-            <SubgraphChip
-              organizationSlug={props.organizationSlug}
-              projectSlug={props.projectSlug}
-              targetSlug={props.targetSlug}
-              key={`${serviceName}-${index}`}
-              text={serviceName}
-              tooltip
-              metadata={meta}
-            />
-          );
-        }),
+        .map((serviceName, index) => renderSubgraphChip(serviceName, index, true)),
-      supergraphMetadata.ownedByServiceNames.map((serviceName, index) => {
-        const meta = supergraphMetadata.metadata?.filter(({ source }) => source === serviceName);
-        return (
-          <SubgraphChip
-            organizationSlug={props.organizationSlug}
-            projectSlug={props.projectSlug}
-            targetSlug={props.targetSlug}
-            key={`${serviceName}-${index}`}
-            text={serviceName}
-            tooltip={false}
-            metadata={meta}
-          />
-        );
-      }),
+      supergraphMetadata.ownedByServiceNames.map((serviceName, index) => 
+        renderSubgraphChip(serviceName, index, false)
+      ),
     ] as const;
   }, [supergraphMetadata.ownedByServiceNames]);

120-126: Consider memoizing metadata filter function.

The metadata filtering function could be memoized separately to prevent unnecessary recalculations.

+  const filterNonSubgraphMetadata = useCallback(
+    (metadata?: Array<{ source?: string }>) => metadata?.filter(m => !m.source),
+    []
+  );

   const meta = useMemo(() => {
-    const nonSubgraphMeta = supergraphMetadata.metadata?.filter(m => !m.source);
+    const nonSubgraphMeta = filterNonSubgraphMetadata(supergraphMetadata.metadata);
     if (!nonSubgraphMeta?.length) {
       return null;
     }
     return <Metadata supergraphMetadata={nonSubgraphMeta} />;
   }, [supergraphMetadata.metadata]);
📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 604e5d4 and 7927741.

⛔ Files ignored due to path filters (1)
  • pnpm-lock.yaml is excluded by !**/pnpm-lock.yaml
📒 Files selected for processing (2)
  • packages/services/api/package.json (1 hunks)
  • packages/web/app/src/components/target/explorer/super-graph-metadata.tsx (6 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
  • packages/services/api/package.json
🧰 Additional context used
🪛 Biome (1.9.4)
packages/web/app/src/components/target/explorer/super-graph-metadata.tsx

[error] 26-32: Avoid using unnecessary Fragment.

A fragment is redundant if it contains only one child, or if it is the child of a html element, and is not a keyed fragment.
Unsafe fix: Remove the Fragment

(lint/complexity/noUselessFragments)

⏰ Context from checks skipped due to timeout of 90000ms (10)
  • GitHub Check: alpha / npm / snapshot
  • GitHub Check: test / unit
  • GitHub Check: typescript / typecheck
  • GitHub Check: static-analysis / analyze (typescript)
  • GitHub Check: storybook-preview / deployment
  • GitHub Check: static-analysis / analyze (javascript)
  • GitHub Check: build / dockerize (linux/arm64)
  • GitHub Check: build / dockerize (linux/amd64)
  • GitHub Check: code-style / eslint-and-prettier
  • GitHub Check: alpha / cli-artifacts
🔇 Additional comments (2)
packages/web/app/src/components/target/explorer/super-graph-metadata.tsx (2)

40-88: LGTM! Clean implementation of metadata support.

The changes to SubgraphChip are well-implemented:

  • Proper type safety with optional metadata
  • Correct key handling in the map function
  • Clear visual indicator (*) for items with metadata

90-99: LGTM! Clear and concise fragment definition.

The GraphQL fragment is well-structured and includes all necessary fields for metadata handling.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 0

🧹 Nitpick comments (4)
packages/web/app/src/components/target/explorer/super-graph-metadata.tsx (4)

19-38: Improve type safety and remove unnecessary Fragment.

Consider these improvements:

  1. Define a proper type for the metadata item.
  2. Remove the unnecessary Fragment wrapper.

Apply this diff:

+type MetadataItem = {
+  name: string;
+  content: string;
+};

-function Metadata(props: { supergraphMetadata: Array<{ name: string; content: string }> }) {
+function Metadata(props: { supergraphMetadata: Array<MetadataItem> }) {
   if (!props.supergraphMetadata.length) {
     return null;
   }
   return (
     <Tooltip
       content={
-        <>
           {props.supergraphMetadata.map((m, i) => (
             <div key={i}>
               <span className="font-bold">{m.name}:</span> {m.content}
             </div>
           ))}
-        </>
       }
     >
       <MetadataIcon className="my-[5px] cursor-pointer text-white" />
     </Tooltip>
   );
 }
🧰 Tools
🪛 Biome (1.9.4)

[error] 26-32: Avoid using unnecessary Fragment.

A fragment is redundant if it contains only one child, or if it is the child of a html element, and is not a keyed fragment.
Unsafe fix: Remove the Fragment

(lint/complexity/noUselessFragments)


76-81: Improve null handling and simplify Fragment usage.

The current implementation has potential issues:

  1. The null check could be more explicit.
  2. The Fragment usage could be simplified to a div.

Apply this diff:

-          {props.metadata?.map((m, index) => (
-            <Fragment key={`${index}`}>
-              <br />
-              <span className="font-bold">{m.content}</span> {m.name}
-            </Fragment>
-          )) ?? null}
+          {props.metadata && props.metadata.length > 0 && (
+            <div className="mt-2">
+              {props.metadata.map((m, index) => (
+                <div key={`metadata-${index}`}>
+                  <span className="font-bold">{m.content}</span> {m.name}
+                </div>
+              ))}
+            </div>
+          )}

135-169: Reduce code duplication in mapping functions.

The mapping logic is duplicated between the preview and full list rendering. This could be extracted into a reusable function.

Apply this diff:

+  const renderSubgraphChip = useMemo(() => (serviceName: string, index: number, showTooltip = true) => {
+    const meta = supergraphMetadata.metadata?.filter(({ source }) => source === serviceName);
+    return (
+      <SubgraphChip
+        organizationSlug={props.organizationSlug}
+        projectSlug={props.projectSlug}
+        targetSlug={props.targetSlug}
+        key={`${serviceName}-${index}`}
+        text={serviceName}
+        tooltip={showTooltip}
+        metadata={meta}
+      />
+    );
+  }, [props.organizationSlug, props.projectSlug, props.targetSlug, supergraphMetadata.metadata]);

   const items = useMemo(() => {
     if (supergraphMetadata.ownedByServiceNames == null) {
       return null;
     }

     if (supergraphMetadata.ownedByServiceNames.length <= previewThreshold) {
       return [
-        supergraphMetadata.ownedByServiceNames.map((serviceName, index) => {
-          const meta = supergraphMetadata.metadata?.filter(({ source }) => source === serviceName);
-          return (
-            <SubgraphChip
-              organizationSlug={props.organizationSlug}
-              projectSlug={props.projectSlug}
-              targetSlug={props.targetSlug}
-              key={`${serviceName}-${index}`}
-              text={serviceName}
-              tooltip
-              metadata={meta}
-            />
-          );
-        }),
+        supergraphMetadata.ownedByServiceNames.map((serviceName, index) => 
+          renderSubgraphChip(serviceName, index)
+        ),
         null,
       ] as const;
     }

     return [
       supergraphMetadata.ownedByServiceNames
         .slice(0, previewThreshold)
-        .map((serviceName, index) => {
-          const meta = supergraphMetadata.metadata?.filter(({ source }) => source === serviceName);
-          return (
-            <SubgraphChip
-              organizationSlug={props.organizationSlug}
-              projectSlug={props.projectSlug}
-              targetSlug={props.targetSlug}
-              key={`${serviceName}-${index}`}
-              text={serviceName}
-              tooltip
-              metadata={meta}
-            />
-          );
-        }),
+        .map((serviceName, index) => renderSubgraphChip(serviceName, index)),

194-194: Consider documenting the delayDuration value.

The delayDuration prop is set to 100ms without explanation. Consider adding a comment explaining why this specific value was chosen.

-    <TooltipProvider delayDuration={100}>
+    {/* 100ms delay provides a good balance between responsiveness and preventing accidental triggers */}
+    <TooltipProvider delayDuration={100}>
📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 7927741 and 87ed83f.

📒 Files selected for processing (1)
  • packages/web/app/src/components/target/explorer/super-graph-metadata.tsx (6 hunks)
🧰 Additional context used
🪛 Biome (1.9.4)
packages/web/app/src/components/target/explorer/super-graph-metadata.tsx

[error] 26-32: Avoid using unnecessary Fragment.

A fragment is redundant if it contains only one child, or if it is the child of a html element, and is not a keyed fragment.
Unsafe fix: Remove the Fragment

(lint/complexity/noUselessFragments)

⏰ Context from checks skipped due to timeout of 90000ms (10)
  • GitHub Check: alpha / npm / snapshot
  • GitHub Check: test / unit
  • GitHub Check: typescript / typecheck
  • GitHub Check: static-analysis / analyze (typescript)
  • GitHub Check: storybook-preview / deployment
  • GitHub Check: static-analysis / analyze (javascript)
  • GitHub Check: build / dockerize (linux/arm64)
  • GitHub Check: code-style / eslint-and-prettier
  • GitHub Check: build / dockerize (linux/amd64)
  • GitHub Check: alpha / cli-artifacts
🔇 Additional comments (1)
packages/web/app/src/components/target/explorer/super-graph-metadata.tsx (1)

56-57: Address the TODO comment about routing.

There's an unresolved TODO comment about routing. Please clarify if this needs to be addressed in this PR.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 0

🧹 Nitpick comments (4)
packages/web/app/src/components/target/explorer/super-graph-metadata.tsx (4)

18-37: Define TypeScript interface for metadata props.

Consider defining a reusable interface for the metadata structure to improve type safety and reusability.

+interface MetadataItem {
+  name: string;
+  content: string;
+}

-function Metadata(props: { supergraphMetadata: Array<{ name: string; content: string }> }) {
+function Metadata(props: { supergraphMetadata: Array<MetadataItem> }) {

Also, consider using a more stable key for the mapped elements:

-          {props.supergraphMetadata.map((m, i) => (
-            <div key={i}>
+          {props.supergraphMetadata.map((m) => (
+            <div key={`${m.name}-${m.content}`}>
🧰 Tools
🪛 Biome (1.9.4)

[error] 25-31: Avoid using unnecessary Fragment.

A fragment is redundant if it contains only one child, or if it is the child of a html element, and is not a keyed fragment.
Unsafe fix: Remove the Fragment

(lint/complexity/noUselessFragments)


25-31: Remove unnecessary Fragment wrapper.

The Fragment wrapper is redundant as it contains only one child element.

-        <>
           {props.supergraphMetadata.map((m, i) => (
             <div key={i}>
               <span className="font-bold">{m.name}:</span> {m.content}
             </div>
           ))}
-        </>
🧰 Tools
🪛 Biome (1.9.4)

[error] 25-31: Avoid using unnecessary Fragment.

A fragment is redundant if it contains only one child, or if it is the child of a html element, and is not a keyed fragment.
Unsafe fix: Remove the Fragment

(lint/complexity/noUselessFragments)


201-210: Add ARIA attributes for better accessibility.

The scrollable content area should have appropriate ARIA attributes for better screen reader support.

-                <div className="absolute inset-0 size-[250px] overflow-y-scroll py-2">
+                <div 
+                  className="absolute inset-0 size-[250px] overflow-y-scroll py-2"
+                  role="list"
+                  aria-label="All subgraphs"
+                >

119-125: Consider extracting metadata filtering logic.

The metadata filtering logic could be extracted into a separate utility function for better reusability and testability.

+const getNonSubgraphMetadata = (metadata?: Array<{ name: string; content: string; source?: string }>) => {
+  return metadata?.filter(m => !m.source) ?? [];
+};

 const meta = useMemo(() => {
-  const nonSubgraphMeta = supergraphMetadata.metadata?.filter(m => !m.source);
-  if (!nonSubgraphMeta?.length) {
+  const nonSubgraphMeta = getNonSubgraphMetadata(supergraphMetadata.metadata);
+  if (!nonSubgraphMeta.length) {
     return null;
   }
   return <Metadata supergraphMetadata={nonSubgraphMeta} />;
 }, [supergraphMetadata.metadata]);
📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 87ed83f and 6c5c165.

⛔ Files ignored due to path filters (1)
  • pnpm-lock.yaml is excluded by !**/pnpm-lock.yaml
📒 Files selected for processing (1)
  • packages/web/app/src/components/target/explorer/super-graph-metadata.tsx (6 hunks)
🧰 Additional context used
🪛 Biome (1.9.4)
packages/web/app/src/components/target/explorer/super-graph-metadata.tsx

[error] 25-31: Avoid using unnecessary Fragment.

A fragment is redundant if it contains only one child, or if it is the child of a html element, and is not a keyed fragment.
Unsafe fix: Remove the Fragment

(lint/complexity/noUselessFragments)

⏰ Context from checks skipped due to timeout of 90000ms (10)
  • GitHub Check: alpha / npm / snapshot
  • GitHub Check: test / unit
  • GitHub Check: static-analysis / analyze (typescript)
  • GitHub Check: typescript / typecheck
  • GitHub Check: storybook-preview / deployment
  • GitHub Check: static-analysis / analyze (javascript)
  • GitHub Check: build / dockerize (linux/arm64)
  • GitHub Check: build / dockerize (linux/amd64)
  • GitHub Check: alpha / cli-artifacts
  • GitHub Check: code-style / eslint-and-prettier
🔇 Additional comments (1)
packages/web/app/src/components/target/explorer/super-graph-metadata.tsx (1)

75-80: Use more meaningful keys for mapped elements.

Using just the index as a key might lead to rendering issues. Consider using a combination of metadata properties for a more stable key.

-            <Fragment key={`${index}`}>
+            <Fragment key={`${m.name}-${m.content}`}>

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Development

Successfully merging this pull request may close these issues.

4 participants