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

feature: add federation-link-util library #6452

Merged
merged 7 commits into from
Feb 12, 2025
Merged

feature: add federation-link-util library #6452

merged 7 commits into from
Feb 12, 2025

Conversation

jdolle
Copy link
Collaborator

@jdolle jdolle commented Jan 31, 2025

Background

This arose from the desire to add a directive for collecting metadata.
Currently there is no great way to ensure you are following the @link spec's defined versioning scheme, and it is very difficult to keep differences between versions straight.

Description

This utility can be used to apply changes to a schema, or collect metadata from a schema. It's very flexible.

@jdolle jdolle self-assigned this Jan 31, 2025

This comment was marked as resolved.

const metaSpec = new LinkableSpec('https://specs.graphql-hive.com/metadata', {
// The return value could be used to map sdl, collect information, or create a graphql yoga plugin.
// In this test, it's used to collect metadata information from the schema.
'v0.1': resolveImportName => (typeDefs: DocumentNode) => {
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 test is the best example of how I intended this API to be used.
The LinkableSpec defines how the linked schemas are implemented.
The Link. resolveImportName function is then passed to the handler to be used to resolve the names of nodes as they are referred to within the subgraph... I chose to only pass resolveImportName to keep the API simpler.
And then the implementation uses that to figure out the names of things it supports.

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

coderabbitai bot commented Feb 3, 2025

📝 Walkthrough

Summary by CodeRabbit

  • New Features

    • Launched a new federation link utilities package that enhances GraphQL federation support with improved URL parsing, version handling, and import alias resolution.
    • Upgraded the schema composition process, resulting in more robust error handling and streamlined tag filtering.
  • Documentation

    • Rolled out comprehensive documentation to guide users in leveraging the new federation features.
  • Chores

    • Updated project configuration and dependencies for seamless integration of these federation improvements.

Walkthrough

The pull request introduces enhancements to the federation-link-utils package, which facilitates the management of GraphQL federation link directives. Key changes include a new path mapping in the TypeScript configuration, comprehensive utility functions for extracting link definitions, resolving import names, managing versioned URLs, and matching version implementations. Additionally, new documentation, package metadata, configuration files, and extensive test suites are included to support these functionalities.

Changes

File(s) Change Summary
tsconfig.json Added new path mapping for @graphql-hive/federation-link-utils pointing to ./packages/libraries/federation-link-utils/src/index.ts.
packages/libraries/federation-link-utils/{README.md, package.json, tsconfig.json} Introduced new package documentation, metadata, and TypeScript configuration for the federation-link-utils library.
packages/libraries/federation-link-utils/src/... Added new utility modules including index.ts (with extractLinkImplementations, FEDERATION_V1, and LinkVersion), link-import.ts (with FederatedLinkImport class), link-url.ts (with FederatedLinkUrl class and version parsing/support methods), link.ts (with FederatedLink class and related methods), and version.ts (exporting the package version).
packages/libraries/federation-link-utils/tests/... Added test suites for validating extractLinkImplementations, FederatedLinkUrl, and FederatedLink functionalities using various SDL and URL scenarios.
packages/services/schema/package.json Added dependency on @graphql-hive/federation-link-utils in devDependencies.
packages/services/schema/src/api.ts Updated import statement for CompositionFailureError and CompositionErrorSource types from ./orchestrators to ./lib/errors.
packages/services/schema/src/lib/... Introduced new files and functions for handling GraphQL schema composition, including add-inaccessible-to-unreachable-types.ts, compose.ts, and errors.ts, along with corresponding test files.
packages/services/schema/src/orchestrators.ts Removed several imports and updated the composition logic to utilize new methods defined in the lib/compose module.
packages/services/schema/src/types.ts Updated import source for CompositionErrorSource type.

Sequence Diagram(s)

sequenceDiagram
    participant Client as Client
    participant EL as extractLinkImplementations
    participant FL as FederatedLink.fromTypedefs
    participant FI as FederatedLinkImport

    Client->>EL: Provide GraphQL DocumentNode
    EL->>FL: Extract @link directives from typedefs
    FL->>FI: Parse link imports and namespacing
    FI-->>FL: Return parsed link imports
    FL-->>EL: Return collection of FederatedLink instances
    EL-->>Client: Provide resolveImportName & matchesImplementation functions
Loading
sequenceDiagram
    participant URLStr as URL String
    participant FUrl as FederatedLinkUrl.fromUrl
    participant FUrlObj as FederatedLinkUrl

    URLStr->>FUrl: Parse URL string
    FUrl->>FUrlObj: Extract identity, name, and version
    note right of FUrlObj: Perform internal version parsing and compatibility checks
    FUrlObj-->>FUrl: Return FederatedLinkUrl instance
    FUrlObj->>FUrlObj: Invoke supports(...) for version check
    FUrlObj-->>Caller: Return compatibility result
Loading

Thank you for using CodeRabbit. We offer it for free to the OSS community and would appreciate your support in helping us grow. If you find it useful, would you consider giving us a shout-out on your favorite social media?

❤️ Share
🪧 Tips

Chat

There are 3 ways to chat with CodeRabbit:

  • Review comments: Directly reply to a review comment made by CodeRabbit. Example:
    • I pushed a fix in commit <commit_id>, please review it.
    • Generate unit testing code for this file.
    • Open a follow-up GitHub issue for this discussion.
  • Files and specific lines of code (under the "Files changed" tab): Tag @coderabbitai in a new review comment at the desired location with your query. Examples:
    • @coderabbitai generate unit testing code for this file.
    • @coderabbitai modularize this function.
  • PR comments: Tag @coderabbitai in a new PR comment to ask questions about the PR branch. For the best results, please provide a very specific query, as very limited context is provided in this mode. Examples:
    • @coderabbitai gather interesting stats about this repository and render them as a table. Additionally, render a pie chart showing the language distribution in the codebase.
    • @coderabbitai read src/utils.ts and generate unit testing code.
    • @coderabbitai read the files in the src/scheduler package and generate a class diagram using mermaid and a README in the markdown format.
    • @coderabbitai help me debug CodeRabbit configuration file.

Note: Be mindful of the bot's finite context window. It's strongly recommended to break down tasks such as reading entire modules into smaller chunks. For a focused discussion, use review comments to chat about specific files and their changes, instead of using the PR comments.

CodeRabbit Commands (Invoked using PR comments)

  • @coderabbitai pause to pause the reviews on a PR.
  • @coderabbitai resume to resume the paused reviews.
  • @coderabbitai review to trigger an incremental review. This is useful when automatic reviews are disabled for the repository.
  • @coderabbitai full review to do a full review from scratch and review all the files again.
  • @coderabbitai summary to regenerate the summary of the PR.
  • @coderabbitai resolve resolve all the CodeRabbit review comments.
  • @coderabbitai configuration to show the current CodeRabbit configuration for the repository.
  • @coderabbitai help to get help.

Other keywords and placeholders

  • Add @coderabbitai ignore anywhere in the PR description to prevent this PR from being reviewed.
  • Add @coderabbitai summary to generate the high-level summary at a specific location in the PR description.
  • Add @coderabbitai anywhere in the PR title to generate the title automatically.

CodeRabbit Configuration File (.coderabbit.yaml)

  • You can programmatically configure CodeRabbit by adding a .coderabbit.yaml file to the root of your repository.
  • Please see the configuration documentation for more information.
  • If your editor has YAML language server enabled, you can add the path at the top of this file to enable auto-completion and validation: # yaml-language-server: $schema=https://coderabbit.ai/integrations/schema.v2.json

Documentation and Community

  • Visit our Documentation for detailed information on how to use CodeRabbit.
  • Join our Discord Community to get help, request features, and share feedback.
  • Follow us on X/Twitter for updates and announcements.

coderabbitai[bot]

This comment was marked as resolved.

@jdolle jdolle force-pushed the linkable-spec-lib branch from 66dfc13 to bf8c6e8 Compare February 3, 2025 03:36
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 (6)
packages/libraries/linkable-specs/src/__tests__/index.spec.ts (3)

1-17: LGTM! Consider adding JSDoc comments.

The test setup provides a clear example of the API usage with proper imports and SDL definition. Consider adding JSDoc comments to document the test's purpose and the demonstrated functionality.

+/**
+ * Tests the integration between LinkableSpec and detectLinkedImplementations
+ * demonstrating how to implement versioned metadata collection from linked schemas.
+ */
 test('LinkableSpec and detectLinkedImplementations can be used to easily implement linked schema functionality', () => {

32-44: Extract repeated directive argument extraction logic.

The pattern for extracting string values from directive arguments is duplicated. Consider extracting this into a helper function to improve maintainability and reduce duplication.

+const getDirectiveStringArg = (directive: any, argName: string) => 
+  (directive.arguments?.find(a => a.name.value === argName)?.value as StringValueNode | undefined)?.value ?? '??';

-metaData['name'] =
-  (
-    meta.arguments?.find(a => a.name.value === 'name')?.value as
-      | StringValueNode
-      | undefined
-  )?.value ?? '??';
-metaData['content'] =
-  (
-    meta.arguments?.find(a => a.name.value === 'content')?.value as
-      | StringValueNode
-      | undefined
-  )?.value ?? '??';
+metaData['name'] = getDirectiveStringArg(meta, 'name');
+metaData['content'] = getDirectiveStringArg(meta, 'content');

-metaData['eg'] =
-  (
-    example.arguments?.find(a => a.name.value === 'eg')?.value as
-      | StringValueNode
-      | undefined
-  )?.value ?? '??';
+metaData['eg'] = getDirectiveStringArg(example, 'eg');

Also applies to: 48-54


65-68: Add TODO comment for v0.2 implementation.

The v0.2 implementation is currently a placeholder. Consider adding a TODO comment to track this for future implementation.

-'v0.2': _resolveImportName => (_typeDefs: DocumentNode) => {
+'v0.2': _resolveImportName => (_typeDefs: DocumentNode) => {
+  // TODO: Implement v0.2 metadata collection logic
   // collect metadata
   return `running on v0.2...`;
 },
packages/libraries/linkable-specs/src/link.ts (3)

5-32: Consider explicit handling of unknown arguments or removing the commented-out log.
Currently, unrecognized arguments trigger the default case (line 23) with a commented-out console.warn. Either remove the commented log or convert it into a meaningful handling or user feedback mechanism to clarify unsupported arguments.

   default: {
-    // console.warn('Unknown argument');
+    console.warn(`Unknown argument: ${arg.name.value}`);
   }

44-66: Ensure the @link directive is relevant to the intended specification.
While filtering directives named 'link' is correct for Apollo’s federated spec, be cautious if multiple specifications or custom 'link' directives exist. Make sure that the directive name matches the intended spec usage or version.


85-119: Replace ts-expect-error with a more robust type definition.
The overload notation for supports() could be handled via a single function signature that performs runtime checks on its parameters. This would eliminate the need for ts-expect-error and improve type clarity.

-  supports(...args: [string] | [number, number] | [FederatedLinkUrl]): boolean {
-    /** @ts-expect-error: ignore tuple error. These are tuples and can be spread. tsc is wrong. */
-    return this.url.supports(...args);
-  }
+  supports(version: string | number | FederatedLinkUrl, minor?: number): boolean {
+    if (typeof version === 'string') {
+      return this.url.supports(version);
+    } else if (typeof version === 'number' && typeof minor === 'number') {
+      return this.url.supports(version, minor);
+    } else {
+      return this.url.supports(version);
+    }
+  }
📜 Review details

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

📥 Commits

Reviewing files that changed from the base of the PR and between 66dfc13 and bf8c6e8.

⛔ Files ignored due to path filters (1)
  • pnpm-lock.yaml is excluded by !**/pnpm-lock.yaml
📒 Files selected for processing (12)
  • packages/libraries/linkable-specs/README.md (1 hunks)
  • packages/libraries/linkable-specs/package.json (1 hunks)
  • packages/libraries/linkable-specs/src/__tests__/index.spec.ts (1 hunks)
  • packages/libraries/linkable-specs/src/__tests__/link-url.spec.ts (1 hunks)
  • packages/libraries/linkable-specs/src/__tests__/link.spec.ts (1 hunks)
  • packages/libraries/linkable-specs/src/__tests__/linkable-spec.spec.ts (1 hunks)
  • packages/libraries/linkable-specs/src/index.ts (1 hunks)
  • packages/libraries/linkable-specs/src/link-import.ts (1 hunks)
  • packages/libraries/linkable-specs/src/link-url.ts (1 hunks)
  • packages/libraries/linkable-specs/src/link.ts (1 hunks)
  • packages/libraries/linkable-specs/src/linkable-spec.ts (1 hunks)
  • packages/libraries/linkable-specs/tsconfig.json (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (10)
  • packages/libraries/linkable-specs/README.md
  • packages/libraries/linkable-specs/tsconfig.json
  • packages/libraries/linkable-specs/src/tests/linkable-spec.spec.ts
  • packages/libraries/linkable-specs/src/tests/link.spec.ts
  • packages/libraries/linkable-specs/src/index.ts
  • packages/libraries/linkable-specs/src/tests/link-url.spec.ts
  • packages/libraries/linkable-specs/src/link-import.ts
  • packages/libraries/linkable-specs/package.json
  • packages/libraries/linkable-specs/src/link-url.ts
  • packages/libraries/linkable-specs/src/linkable-spec.ts
🧰 Additional context used
📓 Path-based instructions (2)
packages/libraries/linkable-specs/src/__tests__/index.spec.ts (1)

Pattern 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/linkable-specs/src/link.ts (1)

Pattern 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.

🔇 Additional comments (4)
packages/libraries/linkable-specs/src/__tests__/index.spec.ts (1)

70-78: Consider adding more test cases.

The current test only covers the happy path for v0.1. Consider adding test cases for:

  • v0.2 implementation
  • Error cases (malformed directives, missing required arguments)
  • Edge cases (no directives, multiple directives)

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

packages/libraries/linkable-specs/src/link.ts (3)

1-4: Imports look clean and straightforward.
No issues found in these imports; they accurately capture the dependencies needed to handle ConstArgumentNode and related types.


34-42: Namespace function logic is clear.
The logic for prefixing names with a namespace is succinct and easy to read. No immediate concerns.


68-84: Validate or sanitize user-provided identifiers in toString().
Because toString() directly interpolates url and imports into an SDL string, confirm that upstream code or the parsing logic won’t cause malformed SDL or injection-like concerns if the input is external.

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 (2)
packages/libraries/linkable-specs/src/link-import.ts (2)

9-11: Add JSDoc documentation for the toString method.

While the implementation is correct, adding documentation would improve clarity about the expected string format.

+  /**
+   * Returns a string representation of the import in GraphQL SDL format.
+   * @returns {string} For imports with alias: { name: "name", as: "alias" }
+   *                   For imports without alias: "name"
+   */
   public toString(): string {

13-47: Add JSDoc documentation and empty string validation.

  1. Add JSDoc documentation for better API understanding.
  2. Add validation for empty strings in the 'name' field.
+  /**
+   * Parses GraphQL ConstValueNode to extract @link import declarations.
+   * @param {ConstValueNode} node - The node containing import declarations
+   * @returns {FederatedLinkImport[]} Array of parsed import declarations
+   * @throws {Error} If the node structure is invalid
+   */
   static fromTypedefs(node: ConstValueNode): FederatedLinkImport[] {

     // ... existing code ...

             name = f.value.value;
+            if (name.trim() === '') {
+              throw new Error('Empty string is not allowed for @link "name" field');
+            }
📜 Review details

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

📥 Commits

Reviewing files that changed from the base of the PR and between bf8c6e8 and 67f0137.

⛔ Files ignored due to path filters (1)
  • pnpm-lock.yaml is excluded by !**/pnpm-lock.yaml
📒 Files selected for processing (1)
  • packages/libraries/linkable-specs/src/link-import.ts (1 hunks)
🧰 Additional context used
📓 Path-based instructions (1)
packages/libraries/linkable-specs/src/link-import.ts (1)

Pattern 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.

📓 Learnings (1)
📓 Common learnings
Learnt from: jdolle
PR: graphql-hive/console#6452
File: packages/libraries/linkable-specs/src/__tests__/index.spec.ts:25-25
Timestamp: 2025-02-03T03:41:50.270Z
Learning: The @graphql-hive/linkable-specs package handles directive namespacing automatically through its resolveImportName function. Users should use base directive names (e.g., '@example') rather than fully qualified names (e.g., '@metadata_example') as the package manages the namespace transformation internally.
🔇 Additional comments (2)
packages/libraries/linkable-specs/src/link-import.ts (2)

1-7: LGTM! Clean and concise class declaration.

The imports are appropriate, and the constructor uses TypeScript's parameter property syntax effectively.


17-17: Replace 'this' with class name in static context.

Using 'this' in static methods can be confusing as it refers to the class itself.

-          return new this(v.value, null);
+          return new FederatedLinkImport(v.value, null);
-          return new this(name, as);
+          return new FederatedLinkImport(name, as);

Also applies to: 40-40

@jdolle jdolle requested review from kamilkisiela and n1ru4l February 3, 2025 03:48
Copy link
Member

@jasonkuhrt jasonkuhrt left a comment

Choose a reason for hiding this comment

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

Hey @jdolle my review is accumulating some questions and there's still a ways to go. Going to flush what I have so far in case you could already answer some. It would probably help me better understand what is going on etc. :)

Comment on lines 3 to 5
[Hive](https://the-guild.dev/graphql/hive) is a fully open-source schema registry, analytics,
metrics and gateway for [GraphQL federation](https://the-guild.dev/graphql/hive/federation) and
other GraphQL APIs.
Copy link
Member

@jasonkuhrt jasonkuhrt Feb 3, 2025

Choose a reason for hiding this comment

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

Wondering if could make this even more maintainable by avoiding specific feature references, e.g.

... is a delightful MIT licensed platform for developing and operating your GraphQL services from idea to production, solo to enterprise.

And maybe there is/this is a template we can turn into a copy standard? CC @dotansimha @Urigo

packages/libraries/linkable-specs/README.md Outdated Show resolved Hide resolved
coderabbitai[bot]

This comment was marked as resolved.

coderabbitai[bot]

This comment was marked as resolved.

@jasonkuhrt

This comment was marked as resolved.

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 (2)
packages/libraries/linkable-specs/README.md (2)

9-12: Grammar Correction in Custom Features Description

The sentence “This library can be used to make a custom features for GraphQL schemas backed by Federation's directive.” contains a grammatical issue with the article used before the plural noun “features.” Consider revising to either:

  • “This library can be used to create custom features for GraphQL schemas…”
    or
  • “This library can be used to make a custom feature for GraphQL schemas…”

This change improves clarity and correctness.

🧰 Tools
🪛 LanguageTool

[grammar] ~9-~9: Do not use the singular ‘a’ before the plural noun ‘features’.
Context: ... --- This library can be used to make a custom features for GraphQL schemas backed by Federatio...

(VB_A_JJ_NNS)


66-68: Clarify the "Yoga Plugin" Recommendation

The note stating “always return a yoga plugin” might be ambiguous to users who are not familiar with the term. Consider providing a brief explanation or a hyperlink to documentation that explains what a yoga plugin is and why it is recommended.

📜 Review details

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

📥 Commits

Reviewing files that changed from the base of the PR and between f8df0a0 and 36584a1.

📒 Files selected for processing (1)
  • packages/libraries/linkable-specs/README.md (1 hunks)
🧰 Additional context used
📓 Path-based instructions (1)
packages/libraries/linkable-specs/README.md (1)

Pattern 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.

📓 Learnings (2)
📓 Common learnings
Learnt from: jdolle
PR: graphql-hive/console#6452
File: packages/libraries/linkable-specs/src/__tests__/index.spec.ts:25-25
Timestamp: 2025-02-03T03:41:50.270Z
Learning: The @graphql-hive/linkable-specs package handles directive namespacing automatically through its resolveImportName function. Users should use base directive names (e.g., '@example') rather than fully qualified names (e.g., '@metadata_example') as the package manages the namespace transformation internally.
packages/libraries/linkable-specs/README.md (1)
Learnt from: jdolle
PR: graphql-hive/console#6452
File: packages/libraries/linkable-specs/src/__tests__/index.spec.ts:25-25
Timestamp: 2025-02-03T03:41:50.270Z
Learning: The @graphql-hive/linkable-specs package handles directive namespacing automatically through its resolveImportName function. Users should use base directive names (e.g., '@example') rather than fully qualified names (e.g., '@metadata_example') as the package manages the namespace transformation internally.
🪛 LanguageTool
packages/libraries/linkable-specs/README.md

[grammar] ~9-~9: Do not use the singular ‘a’ before the plural noun ‘features’.
Context: ... --- This library can be used to make a custom features for GraphQL schemas backed by Federatio...

(VB_A_JJ_NNS)

⏰ Context from checks skipped due to timeout of 90000ms (9)
  • GitHub Check: test / unit
  • GitHub Check: typescript / typecheck
  • GitHub Check: static-analysis / analyze (typescript)
  • 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
  • GitHub Check: SonarCloud
🔇 Additional comments (4)
packages/libraries/linkable-specs/README.md (4)

1-2: Header is Clear and Informative

The header “GraphQL Hive - linkable-specs” clearly identifies the package and purpose.


3-5: Adopt a Consistent Copy Standard for the Introduction

The introductory text provides a good overview; however, consider revising it to follow a consistent copy standard. Previous feedback suggested using a generic template that avoids overly specific feature references. For example, instead of specific claims, a template outlining capabilities in a maintainable way could be beneficial.


13-18: Features Section Looks Good

The bulleted features list is concise and clearly communicates the key capabilities of the package.


19-34: GraphQL Usage Example is Informative

The provided GraphQL schema snippet adequately demonstrates the usage of the @link directive in context. The inclusion of a concrete example helps users understand how to apply the feature.

packages/libraries/linkable-specs/README.md Outdated Show resolved Hide resolved
@jdolle
Copy link
Collaborator Author

jdolle commented Feb 3, 2025

I considered using that package but federation links only have major and minor, have special requirements for v0.x that don't follow semantic versioning, and I didnt want to introduce a dependency for just the one or two relatively simple functions to write. If the link version spec is ever modified to specifically use semantic versions then we can add this package.

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 (2)
packages/libraries/linkable-specs/README.md (2)

9-12: Grammar Correction Needed in Description.

The sentence "This library can be used to make a custom features for GraphQL schemas backed by Federation's" contains a grammatical error ("a custom features"). Consider rephrasing to "This library can be used to create custom features for GraphQL schemas backed by Federation's" (or another phrasing that fits the intended meaning).

Diff Suggestion:

-This library can be used to make a custom features for GraphQL schemas backed by Federation's
+This library can be used to create custom features for GraphQL schemas backed by Federation's
🧰 Tools
🪛 LanguageTool

[grammar] ~9-~9: Do not use the singular ‘a’ before the plural noun ‘features’.
Context: ... --- This library can be used to make a custom features for GraphQL schemas backed by Federatio...

(VB_A_JJ_NNS)


65-68: Clarify Recommendation on Consistent Implementation.

The concluding note recommends that all LinkedSpecs always return a yoga plugin. It would be beneficial to provide a brief explanation or example of why this consistency is important and how it benefits the overall implementation.

📜 Review details

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

📥 Commits

Reviewing files that changed from the base of the PR and between 36584a1 and e93a477.

📒 Files selected for processing (1)
  • packages/libraries/linkable-specs/README.md (1 hunks)
🧰 Additional context used
📓 Path-based instructions (1)
packages/libraries/linkable-specs/README.md (1)

Pattern 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.

📓 Learnings (2)
📓 Common learnings
Learnt from: jdolle
PR: graphql-hive/console#6452
File: packages/libraries/linkable-specs/src/__tests__/index.spec.ts:25-25
Timestamp: 2025-02-03T03:41:50.270Z
Learning: The @graphql-hive/linkable-specs package handles directive namespacing automatically through its resolveImportName function. Users should use base directive names (e.g., '@example') rather than fully qualified names (e.g., '@metadata_example') as the package manages the namespace transformation internally.
packages/libraries/linkable-specs/README.md (1)
Learnt from: jdolle
PR: graphql-hive/console#6452
File: packages/libraries/linkable-specs/src/__tests__/index.spec.ts:25-25
Timestamp: 2025-02-03T03:41:50.270Z
Learning: The @graphql-hive/linkable-specs package handles directive namespacing automatically through its resolveImportName function. Users should use base directive names (e.g., '@example') rather than fully qualified names (e.g., '@metadata_example') as the package manages the namespace transformation internally.
🪛 LanguageTool
packages/libraries/linkable-specs/README.md

[grammar] ~9-~9: Do not use the singular ‘a’ before the plural noun ‘features’.
Context: ... --- This library can be used to make a custom features for GraphQL schemas backed by Federatio...

(VB_A_JJ_NNS)

⏰ Context from checks skipped due to timeout of 90000ms (9)
  • GitHub Check: typescript / typecheck
  • GitHub Check: alpha / cli-artifacts
  • GitHub Check: test / unit
  • GitHub Check: static-analysis / analyze (typescript)
  • 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: SonarCloud
🔇 Additional comments (6)
packages/libraries/linkable-specs/README.md (6)

1-2: Title and Header Are Clear.

The title "GraphQL Hive - linkable-specs" succinctly informs the reader about the package’s purpose.


3-6: Clear Description Provided.

The introductory paragraph effectively describes GraphQL Hive as an open-source schema registry, analytics, and gateway for GraphQL APIs.


13-18: Features Section is Concise and Informative.

The bullet points clearly list the package’s key capabilities: link version support, import as/namespacing following the link spec, and maintaining minimal dependencies.


19-20: Usage Section Header is Clear.

The dedicated "Usage" section helps users quickly locate practical examples.


21-35: GraphQL Schema Example Effectively Demonstrates Usage.

The provided GraphQL code block clearly illustrates how to define a directive and utilize the @link directive. This practical example aligns well with the package’s objective of managing schemas with custom link features.


36-64: TypeScript Example Illustrates Implementation Correctly.

The TypeScript example demonstrates the instantiation of LinkableSpec and the resolution of directive names using resolveImportName, which is consistent with the automatic namespacing feature noted in the retrieved learnings. Ensure that functions like visit, parse, and detectLinkedImplementations are either included in the context or referenced in documentation for clarity.

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 (9)
packages/libraries/linkable-specs/README.md (3)

9-11: Grammar Correction in Usage Description

The sentence "This library can be used to make a custom features for GraphQL schemas backed by Federation's" contains a grammatical error due to the use of the singular article "a" before the plural noun "features." Consider revising it to either "make custom features" (if plural is intended) or "make a custom feature" (if singular is intended).

🧰 Tools
🪛 LanguageTool

[grammar] ~9-~9: Do not use the singular ‘a’ before the plural noun ‘features’.
Context: ... --- This library can be used to make a custom features for GraphQL schemas backed by Federatio...

(VB_A_JJ_NNS)


36-63: TypeScript Example: Usage Clarity and Completeness

The TypeScript sample clearly illustrates how to create and configure a LinkableSpec instance, resolve a base directive name (e.g., @example), and extract metadata via traversing type definitions. As this snippet is intended for documentation purposes, consider adding import statements or comments indicating where functions (such as visit, parse) and types (DocumentNode, StringValueNode) are sourced from. This can help users better adapt the example into a production context.


65-67: Concluding Remarks and Consistency

The concluding section effectively communicates that the LinkableSpec is unopinionated regarding implementation while recommending consistency across all linked specs (e.g., always returning a yoga plugin). For enhanced clarity and in alignment with internal documentation styles, consider reviewing the phrasing (for example, verifying the use of "I.e." or "i.e.," as per your copy standards).

packages/libraries/linkable-specs/src/link-url.ts (4)

1-10: Consider tightening version constraints.

The version regex pattern is quite permissive, allowing major versions up to 99 and minor versions up to 9999. Consider adding numeric range validation to ensure versions stay within reasonable bounds for federation links.

-const VERSION_MATCH = /v(\d{1,2})\.(\d{1,4})/i;
+const VERSION_MATCH = /v(\d{1,2})\.(\d{1,3})/i;

 function parseVersion(version: string | null): [number, number] {
   const versionParts = version?.match(VERSION_MATCH);
   if (versionParts?.length) {
     const [_full, major, minor] = versionParts;
+    const majorNum = Number(major);
+    const minorNum = Number(minor);
+    if (majorNum > 20 || minorNum > 999) {
+      return [-1, -1];
+    }
-    return [Number(major), Number(minor)];
+    return [majorNum, minorNum];
   }
   return [-1, -1];
 }

12-29: Enhance class documentation.

The class documentation could be more detailed about the parameters and their purpose.

 /**
  * A wrapper around the `@link` url -- this parses all necessary data to identify the link
  * and determine which version is most appropriate to use.
+ *
+ * @param identity - The base URL or identifier of the link
+ * @param name - Optional name of the link specification
+ * @param version - Optional version string in the format 'vX.Y'
  */

53-75: Improve error message specificity.

The error message for unsupported version arguments could be more helpful by indicating the expected types.

-      throw new Error(`Unsupported version argument: ${args}.`);
+      throw new Error(
+        `Unsupported version argument: ${args}. Expected a version string (e.g., 'v1.0'), ` +
+        `version numbers (major, minor), or FederatedLinkUrl instance.`
+      );

77-85: Document version compatibility rules.

The version compatibility logic, especially the special handling of v0.x versions, should be documented for clarity.

+  /**
+   * Checks if this version is compatible with the given version.
+   * 
+   * Version compatibility rules:
+   * - For v0.x: Versions must match exactly (no backward compatibility)
+   * - For v1.x and above: Compatible if major versions match and current minor version >= given minor version
+   */
   private isCompatibleVersion(major: number, minor: number): boolean {
packages/libraries/linkable-specs/src/link.ts (2)

42-50: Add JSDoc documentation and input validation.

The utility function would benefit from proper documentation and input validation.

+/**
+ * Prefixes a name with a namespace if provided.
+ * @param namespace - The namespace to prefix the name with
+ * @param name - The name to be namespaced
+ * @returns The namespaced name or the original name if no namespace is provided
+ * @throws {Error} If name is empty or contains invalid characters
+ */
 function namespaced(namespace: string | null, name: string) {
+  if (!name || typeof name !== 'string') {
+    throw new Error('Name must be a non-empty string');
+  }
+
   if (namespace?.length) {
     if (name.startsWith('@')) {
       return `@${namespace}__${name.substring(1)}`;
     }
     return `${namespace}__${name}`;
   }
   return name;
 }

120-130: Simplify resolveImportName logic and improve error handling.

The method contains complex string manipulation and could benefit from clearer logic separation.

 resolveImportName(elementName: string): string {
+  if (!elementName) {
+    throw new Error('Element name must be provided');
+  }
+
+  // Handle default link import case
   if (this.url.name && elementName === `@${this.url.name}`) {
-    // @note: default is a directive... So remove the `@`
-    return this.defaultImport!.substring(1);
+    const defaultImport = this.defaultImport;
+    if (!defaultImport) {
+      throw new Error('Default import not available');
+    }
+    return defaultImport.substring(1);
   }
+
+  // Find explicit import
   const imported = this.imports.find(i => i.name === elementName);
-  let resolvedName = imported?.as ?? imported?.name ?? namespaced(this.namespace, elementName);
-  // Strip the `@` prefix for directives because in all implementations of mapping or visiting a schema,
-  // directive names are not prefixed with `@`. The `@` is only for SDL.
+  const resolvedName = imported?.as ?? imported?.name ?? namespaced(this.namespace, elementName);
+
+  // Remove @ prefix for directives as they're not used in schema visitors
   return resolvedName.startsWith('@') ? resolvedName.substring(1) : resolvedName;
 }
📜 Review details

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

📥 Commits

Reviewing files that changed from the base of the PR and between e93a477 and 88ed00d.

⛔ Files ignored due to path filters (1)
  • pnpm-lock.yaml is excluded by !**/pnpm-lock.yaml
📒 Files selected for processing (3)
  • packages/libraries/linkable-specs/README.md (1 hunks)
  • packages/libraries/linkable-specs/src/link-url.ts (1 hunks)
  • packages/libraries/linkable-specs/src/link.ts (1 hunks)
🧰 Additional context used
📓 Path-based instructions (3)
packages/libraries/linkable-specs/README.md (1)

Pattern 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/linkable-specs/src/link-url.ts (1)

Pattern 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/linkable-specs/src/link.ts (1)

Pattern 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.

📓 Learnings (2)
📓 Common learnings
Learnt from: jdolle
PR: graphql-hive/console#6452
File: packages/libraries/linkable-specs/src/__tests__/index.spec.ts:25-25
Timestamp: 2025-02-03T03:41:50.270Z
Learning: The @graphql-hive/linkable-specs package handles directive namespacing automatically through its resolveImportName function. Users should use base directive names (e.g., '@example') rather than fully qualified names (e.g., '@metadata_example') as the package manages the namespace transformation internally.
packages/libraries/linkable-specs/README.md (1)
Learnt from: jdolle
PR: graphql-hive/console#6452
File: packages/libraries/linkable-specs/src/__tests__/index.spec.ts:25-25
Timestamp: 2025-02-03T03:41:50.270Z
Learning: The @graphql-hive/linkable-specs package handles directive namespacing automatically through its resolveImportName function. Users should use base directive names (e.g., '@example') rather than fully qualified names (e.g., '@metadata_example') as the package manages the namespace transformation internally.
🪛 LanguageTool
packages/libraries/linkable-specs/README.md

[grammar] ~9-~9: Do not use the singular ‘a’ before the plural noun ‘features’.
Context: ... --- This library can be used to make a custom features for GraphQL schemas backed by Federatio...

(VB_A_JJ_NNS)

⏰ Context from checks skipped due to timeout of 90000ms (12)
  • GitHub Check: alpha / npm / snapshot
  • GitHub Check: alpha / cli-artifacts
  • GitHub Check: test / unit
  • GitHub Check: static-analysis / analyze (typescript)
  • GitHub Check: typescript / typecheck
  • 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: db-types / diff
  • GitHub Check: test / db-migrations
  • GitHub Check: SonarCloud
🔇 Additional comments (6)
packages/libraries/linkable-specs/README.md (3)

1-8: Introduction and Overview Section Review

The introductory section clearly presents the package and its purpose. The title and accompanying description effectively introduce "GraphQL Hive - linkable-specs" and set the context for the utility library.


13-18: Features List Clarity

The features list is well-structured and clearly highlights the package’s functionalities, including link version support, import as/namespacing based on the link spec, and the minimal dependency on graphql.


19-34: GraphQL Code Snippet Validation

The provided GraphQL code snippet is syntactically correct and effectively demonstrates how to declare a directive and extend the schema using the @link directive. The inclusion of relevant hyperlinks enhances clarity and offers users an avenue for further exploration of the concept.

packages/libraries/linkable-specs/src/link-url.ts (1)

31-33: LGTM!

The toString implementation correctly handles both versioned and unversioned URLs.

packages/libraries/linkable-specs/src/link.ts (2)

5-40: Improve type validation and return type.

The function's type validation and return type could be improved for better type safety and clarity.

Consider these improvements:

  1. Add explicit type validation before using values
  2. Make the return type more explicit
  3. Add input validation for required fields
-function linkFromArgs(args: readonly ConstArgumentNode[]): FederatedLink | undefined {
+function linkFromArgs(args: readonly ConstArgumentNode[]): FederatedLink | null {
   let url: FederatedLinkUrl | undefined,
     imports: FederatedLinkImport[] = [],
     as: string | null = null;
+
+  // Validate required arguments
+  const urlArg = args.find(arg => arg.name.value === 'url');
+  if (!urlArg) {
+    throw new Error('Missing required "url" argument in @link directive');
+  }
+
   for (const arg of args) {
     switch (arg.name.value) {
       case 'url': {
-        if (arg.value.kind === Kind.STRING) {
-          url = FederatedLinkUrl.fromUrl(arg.value.value);
-        } else {
-          console.warn(`Unexpected kind, ${arg.value.kind}, for argument "url" in @link.`);
+        if (arg.value.kind !== Kind.STRING) {
+          throw new Error(`Unexpected kind ${arg.value.kind} for argument "url" in @link directive. Expected ${Kind.STRING}.`);
         }
+        url = FederatedLinkUrl.fromUrl(arg.value.value);
         break;
       }
       // ... similar changes for 'as' argument
     }
   }
   if (url !== undefined) {
     return new FederatedLink(url, as, imports);
   }
-  return;
+  return null;
 }

53-53: Address the TODO comment about import names.

The TODO comment raises an important architectural question about import name handling.

Could you clarify if import names are needed for every feature or just the namespace? This decision could impact the implementation's flexibility and performance.

packages/libraries/linkable-specs/src/link-url.ts Outdated Show resolved Hide resolved
packages/libraries/linkable-specs/src/link.ts Outdated Show resolved Hide resolved
packages/libraries/linkable-specs/src/link.ts Outdated Show resolved Hide resolved
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: 2

🧹 Nitpick comments (13)
packages/libraries/federation-link-utils/src/link-url.ts (1)

1-10: Consider supporting patch versions or alternative version formats.
This approach correctly extracts major and minor versions using a concise regex. However, if federated links ever adopt full semantic versioning (including patch versions or more digits), you may need to adjust the regex or your approach.

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

52-132: Potential improvement: incorporate stronger validations or error handling.
While FederatedLink.fromTypedefs collects links effectively, you might consider validating the resulting links (e.g., ensuring they have valid URLs, checking for conflicts) before returning them. This could prevent issues later in the pipeline.

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

5-19: Enhance test coverage with additional test cases.

While the current test verifies basic version compatibility, consider adding test cases for:

  • Edge cases (no matching version)
  • Warning message when version is unsupported
  • Multiple links in the schema
  • Invalid URLs
  • Version compatibility edge cases (v0.x handling)

Example test cases to add:

test('warns when no supporting version is found', () => {
  const spec = new LinkableSpec('https://specs.graphql-hive.com/example', {
    'v1.0': _resolveImportName => 'Version 1.0 used.',
  });
  const sdl = `
    extend schema
      @link(url: "https://specs.graphql-hive.com/example/v2.0")
  `;
  const consoleSpy = jest.spyOn(console, 'warn');
  const links = FederatedLink.fromTypedefs(parse(sdl));
  const specImpl = spec.detectImplementation(links);
  expect(specImpl).toBeUndefined();
  expect(consoleSpy).toHaveBeenCalledWith(expect.stringContaining('Cannot apply @link'));
});

test('handles multiple links in schema', () => {
  const spec = new LinkableSpec('https://specs.graphql-hive.com/example', {
    'v2.0': _resolveImportName => 'Version 2.0 used.',
    'v1.0': _resolveImportName => 'Version 1.0 used.',
  });
  const sdl = `
    extend schema
      @link(url: "https://specs.graphql-hive.com/other/v1.0")
      @link(url: "https://specs.graphql-hive.com/example/v1.1")
  `;
  const links = FederatedLink.fromTypedefs(parse(sdl));
  const specImpl = spec.detectImplementation(links);
  expect(specImpl).toBe('Version 1.0 used.');
});
packages/libraries/federation-link-utils/src/link-import.ts (1)

13-47: Enhance error handling and type safety.

Consider the following improvements:

  1. Add validation for empty name
  2. Use type guards for better type safety
  3. Make error messages more descriptive by including the actual value

Apply this diff to improve the implementation:

 static fromTypedefs(node: ConstValueNode): FederatedLinkImport[] {
   if (node.kind == Kind.LIST) {
     const imports = node.values.map(v => {
       if (v.kind === Kind.STRING) {
+        if (!v.value.trim()) {
+          throw new Error('Empty string is not allowed as @link import name');
+        }
         return new FederatedLinkImport(v.value, null);
       }
       if (v.kind === Kind.OBJECT) {
         let name: string = '';
         let as: string | null = null;

         v.fields.forEach(f => {
           if (f.name.value === 'name') {
             if (f.value.kind !== Kind.STRING) {
               throw new Error(
-                `Expected string value for @link "name" field but got "${f.value.kind}"`,
+                `Expected string value for @link "name" field but got "${f.value.kind}" (${JSON.stringify(f.value)})`,
               );
             }
+            if (!f.value.value.trim()) {
+              throw new Error('Empty string is not allowed as @link import name');
+            }
             name = f.value.value;
           } else if (f.name.value === 'as') {
             if (f.value.kind !== Kind.STRING) {
               throw new Error(
-                `Expected string value for @link "as" field but got "${f.value.kind}"`,
+                `Expected string value for @link "as" field but got "${f.value.kind}" (${JSON.stringify(f.value)})`,
               );
             }
+            if (!f.value.value.trim()) {
+              throw new Error('Empty string is not allowed as @link import alias');
+            }
             as = f.value.value;
           }
         });
+        if (!name) {
+          throw new Error('Missing required "name" field in @link import');
+        }
         return new FederatedLinkImport(name, as);
       }
-      throw new Error(`Unexpected value kind "${v.kind}" in @link import declaration`);
+      throw new Error(`Unexpected value kind "${v.kind}" in @link import declaration (${JSON.stringify(v)})`);
     });
     return imports;
   }
-  throw new Error(`Expected a list of @link imports but got "${node.kind}"`);
+  throw new Error(`Expected a list of @link imports but got "${node.kind}" (${JSON.stringify(node)})`);
 }
packages/libraries/federation-link-utils/src/linkable-spec.ts (3)

15-19: Improve version comparison logic.

The current version comparison assumes a simple major.minor format. Consider:

  1. Adding validation for version format
  2. Handling pre-release versions
  3. Supporting patch versions if needed
 this.sortedVersionKeys = Object.keys(versions).sort((a, b) => {
+  const versionRegex = /^v(\d+)\.(\d+)(?:\.(\d+))?(?:-([0-9A-Za-z-]+(?:\.[0-9A-Za-z-]+)*))?$/;
+  const matchA = a.match(versionRegex);
+  const matchB = b.match(versionRegex);
+  if (!matchA || !matchB) {
+    throw new Error(`Invalid version format: ${!matchA ? a : b}`);
+  }
-  const [aMajor, aMinor] = a.split('.').map(Number);
-  const [bMajor, bMinor] = b.split('.').map(Number);
+  const [, aMajor, aMinor] = matchA.map(Number);
+  const [, bMajor, bMinor] = matchB.map(Number);
   return bMajor !== aMajor ? bMajor - aMajor : bMinor - aMinor;
 });

47-50: Fix typo in warning message.

The warning message contains a duplicate word "compatible".

-          `Available versions: ${this.sortedVersionKeys.join(', ')} and any version these are compatible compatible with.`,
+          `Available versions: ${this.sortedVersionKeys.join(', ')} and any version these are compatible with.`,

39-52: Add JSDoc for public method.

The detectImplementation method would benefit from more detailed documentation.

+  /**
+   * Detects and returns an implementation based on the provided links.
+   * 
+   * @param links - Array of FederatedLink instances to search through
+   * @returns The implementation for the highest compatible version found,
+   *          or undefined if no compatible version is found
+   * @throws {Error} If the version format is invalid
+   * @example
+   * ```typescript
+   * const spec = new LinkableSpec('https://example.com', {
+   *   'v2.0': resolveImportName => new Implementation(resolveImportName),
+   * });
+   * const links = FederatedLink.fromTypedefs(parse(sdl));
+   * const impl = spec.detectImplementation(links);
+   * ```
+   */
   public detectImplementation(links: FederatedLink[]): T | undefined {
packages/libraries/federation-link-utils/src/__tests__/link-url.spec.ts (1)

4-25: Improve test descriptions and add error cases.

While the test coverage is good, consider:

  1. Making test descriptions more descriptive
  2. Adding error cases for invalid URLs
  3. Adding test cases for malformed versions
test.each([
  {
    description: 'parses URL with schema name and version in path',
    url: 'https://spec.example.com/a/b/mySchema/v1.0/',
    expected: {
      identity: 'https://spec.example.com/a/b/mySchema',
      name: 'mySchema',
      version: 'v1.0'
    }
  },
  // ... existing test cases ...
])('fromUrl $description', ({ url, expected }) => {
  const spec = FederatedLinkUrl.fromUrl(url);
  expect(spec.identity).toBe(expected.identity);
  expect(spec.name).toBe(expected.name);
  expect(spec.version).toBe(expected.version);
});

test.each([
  ['invalid://url', 'Invalid URL protocol'],
  ['https://spec.example.com/mySchema/invalid-version', 'Invalid version format'],
])('fromUrl throws error for invalid input: %s', (url, expectedError) => {
  expect(() => FederatedLinkUrl.fromUrl(url)).toThrow(expectedError);
});
packages/libraries/federation-link-utils/README.md (2)

9-11: Fix grammar in the introduction.

The article "a" should not be used before the plural noun "features".

-This library can be used to make a custom features for GraphQL schemas backed by Federation's
+This library can be used to make custom features for GraphQL schemas backed by Federation's
🧰 Tools
🪛 LanguageTool

[grammar] ~9-~9: Do not use the singular ‘a’ before the plural noun ‘features’.
Context: ... --- This library can be used to make a custom features for GraphQL schemas backed by Federatio...

(VB_A_JJ_NNS)


13-67: Consider enhancing the documentation.

While the documentation provides a good overview and usage example, consider adding the following sections to make it more comprehensive:

  • Installation instructions
  • API reference
  • Contributing guidelines
  • License information
packages/libraries/federation-link-utils/src/__tests__/link.spec.ts (2)

12-37: Consider adding negative test cases.

The fromTypedefs test suite only covers successful scenarios. Consider adding test cases for:

  • Invalid SDL syntax
  • Missing or malformed @link directives
  • Empty import arrays

39-71: Add test descriptions for better readability.

The test cases in resolveImportName would benefit from descriptive titles using test.each or separate test blocks for each scenario.

-  test('resolveImportName', () => {
+  describe('resolveImportName', () => {
+    test('resolves aliased imports correctly', () => {
       // Test aliased imports
+    });
+    test('resolves unimported directives with namespace', () => {
       // Test unimported directives
+    });
+    // ... more test cases
   });
packages/libraries/federation-link-utils/src/__tests__/index.spec.ts (1)

31-43: Simplify type assertions using optional chaining.

The nested type assertions and null checks can be simplified using optional chaining.

-metaData['name'] =
-  (
-    meta.arguments?.find(a => a.name.value === 'name')?.value as
-      | StringValueNode
-      | undefined
-  )?.value ?? '??';
+metaData['name'] = (meta.arguments?.find(a => a.name.value === 'name')?.value as StringValueNode)?.value ?? '??';
📜 Review details

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

📥 Commits

Reviewing files that changed from the base of the PR and between cc19a7c and 53eb7c5.

📒 Files selected for processing (13)
  • packages/libraries/federation-link-utils/README.md (1 hunks)
  • packages/libraries/federation-link-utils/package.json (1 hunks)
  • packages/libraries/federation-link-utils/src/__tests__/index.spec.ts (1 hunks)
  • packages/libraries/federation-link-utils/src/__tests__/link-url.spec.ts (1 hunks)
  • packages/libraries/federation-link-utils/src/__tests__/link.spec.ts (1 hunks)
  • packages/libraries/federation-link-utils/src/__tests__/linkable-spec.spec.ts (1 hunks)
  • packages/libraries/federation-link-utils/src/index.ts (1 hunks)
  • packages/libraries/federation-link-utils/src/link-import.ts (1 hunks)
  • packages/libraries/federation-link-utils/src/link-url.ts (1 hunks)
  • packages/libraries/federation-link-utils/src/link.ts (1 hunks)
  • packages/libraries/federation-link-utils/src/linkable-spec.ts (1 hunks)
  • packages/libraries/federation-link-utils/src/version.ts (1 hunks)
  • packages/libraries/federation-link-utils/tsconfig.json (1 hunks)
✅ Files skipped from review due to trivial changes (3)
  • packages/libraries/federation-link-utils/src/version.ts
  • packages/libraries/federation-link-utils/package.json
  • packages/libraries/federation-link-utils/tsconfig.json
🧰 Additional context used
📓 Path-based instructions (10)
packages/libraries/federation-link-utils/src/__tests__/link-url.spec.ts (1)

Pattern 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 (1)

Pattern 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/__tests__/index.spec.ts (1)

Pattern 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-import.ts (1)

Pattern 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/__tests__/linkable-spec.spec.ts (1)

Pattern 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/linkable-spec.ts (1)

Pattern 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/__tests__/link.spec.ts (1)

Pattern 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/README.md (1)

Pattern 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-url.ts (1)

Pattern 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 (1)

Pattern 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.

📓 Learnings (3)
packages/libraries/federation-link-utils/src/__tests__/index.spec.ts (1)
Learnt from: jdolle
PR: graphql-hive/console#6452
File: packages/libraries/linkable-specs/src/__tests__/index.spec.ts:25-25
Timestamp: 2025-02-03T03:41:50.270Z
Learning: The @graphql-hive/linkable-specs package handles directive namespacing automatically through its resolveImportName function. Users should use base directive names (e.g., '@example') rather than fully qualified names (e.g., '@metadata_example') as the package manages the namespace transformation internally.
packages/libraries/federation-link-utils/src/__tests__/linkable-spec.spec.ts (1)
Learnt from: jdolle
PR: graphql-hive/console#6452
File: packages/libraries/linkable-specs/src/__tests__/index.spec.ts:25-25
Timestamp: 2025-02-03T03:41:50.270Z
Learning: The @graphql-hive/linkable-specs package handles directive namespacing automatically through its resolveImportName function. Users should use base directive names (e.g., '@example') rather than fully qualified names (e.g., '@metadata_example') as the package manages the namespace transformation internally.
packages/libraries/federation-link-utils/README.md (1)
Learnt from: jdolle
PR: graphql-hive/console#6452
File: packages/libraries/linkable-specs/src/__tests__/index.spec.ts:25-25
Timestamp: 2025-02-03T03:41:50.270Z
Learning: The @graphql-hive/linkable-specs package handles directive namespacing automatically through its resolveImportName function. Users should use base directive names (e.g., '@example') rather than fully qualified names (e.g., '@metadata_example') as the package manages the namespace transformation internally.
🪛 LanguageTool
packages/libraries/federation-link-utils/README.md

[grammar] ~9-~9: Do not use the singular ‘a’ before the plural noun ‘features’.
Context: ... --- This library can be used to make a custom features for GraphQL schemas backed by Federatio...

(VB_A_JJ_NNS)

⏰ Context from checks skipped due to timeout of 90000ms (3)
  • GitHub Check: static-analysis / analyze (typescript)
  • GitHub Check: static-analysis / analyze (javascript)
  • GitHub Check: SonarCloud
🔇 Additional comments (6)
packages/libraries/federation-link-utils/src/link-url.ts (3)

21-29: Constructor usage looks sound.
Storing both the parsed major/minor and the original version string effectively supports flexible version checks while preserving raw data.


35-51: Handle invalid or malformed URLs more gracefully.
Currently, if new URL(urlSource) receives an invalid URL, it will throw an exception. Consider wrapping it in a try/catch and logging or handling the error to avoid unexpected crashes.


53-85: Version compatibility logic is appropriate.
The current rules for version handling (exact match for major=0, otherwise this.minor >= minor) is consistent with the stated requirements. This ensures backward compatibility of minor versions while forcing exact matches for v0.x.

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

5-40: Validate unknown argument handling.
In linkFromArgs, unrecognized arguments are silently ignored. Ensure that ignoring these arguments aligns with desired behavior, or add warnings for unexpected parameters if needed.


42-50: Utility function is straightforward.
The namespaced function is straightforward and well-documented. It correctly separates directives from normal types by adjusting the "@" prefix.

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

10-20: Function design is clear and modular.
detectLinkedImplementations provides a simple, modular approach to link detection. It neatly delegates link parsing to FederatedLink.fromTypedefs and leverages each supportedSpec to detect potential matches, keeping concerns well-separated.

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/libraries/federation-link-utils/src/link.ts (3)

5-40: Consider more robust error or fallback handling for missing or invalid arguments.
Currently, if url is not a valid string, the function returns undefined, silently skipping link creation. You might consider either throwing an error or returning a diagnostic result to make the behavior explicit for callers.


42-50: Function name clarity.
The function namespaced is concise and descriptive, but it may be clearer to name it something like addNamespacePrefix to communicate exactly what it does.


52-131: Use a configurable logger or error mechanism instead of console.warn.
Using console.warn (lines 15, 27) directly in a library can be noisy for consumers. A configurable logger or custom error-handling mechanism might give library users more control over log output.

📜 Review details

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

📥 Commits

Reviewing files that changed from the base of the PR and between 2de886f and a84e735.

📒 Files selected for processing (2)
  • packages/libraries/federation-link-utils/src/index.ts (1 hunks)
  • packages/libraries/federation-link-utils/src/link.ts (1 hunks)
🧰 Additional context used
📓 Path-based instructions (2)
packages/libraries/federation-link-utils/src/index.ts (1)

Pattern 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 (1)

Pattern 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.

⏰ Context from checks skipped due to timeout of 90000ms (10)
  • GitHub Check: test / unit
  • GitHub Check: typescript / typecheck
  • GitHub Check: static-analysis / analyze (typescript)
  • GitHub Check: static-analysis / analyze (javascript)
  • GitHub Check: test / db-migrations
  • GitHub Check: build / dockerize (linux/arm64)
  • GitHub Check: build / dockerize (linux/amd64)
  • GitHub Check: alpha / cli-artifacts
  • GitHub Check: code-style / eslint-and-prettier
  • GitHub Check: SonarCloud
🔇 Additional comments (3)
packages/libraries/federation-link-utils/src/link.ts (1)

1-4: Looks well-structured and provides clear imports.
No issues found in these lines.

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

1-9: Import and export declarations look good.
They cleanly expose the library’s capabilities.


10-21: Verify error handling for partial or invalid spec detection.
If a detectImplementation call throws an exception or returns a partial result, it might be beneficial to handle or log that scenario.

Would you like to run a codebase analysis script to identify places where detectImplementation might fail or return unexpected values?

Copy link
Contributor

github-actions bot commented Feb 4, 2025

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

Targets: build

Platforms: linux/amd64

Image Tag: e345a1310373345bc0f1b9e74c8ed18def212e68

Docker Bake metadata
{
"app": {
  "buildx.build.provenance": {
    "buildType": "https://mobyproject.org/buildkit@v1",
    "materials": [
      {
        "uri": "pkg:docker/node@22.13.0-slim?platform=linux%2Famd64",
        "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": "e345a1310373345bc0f1b9e74c8ed18def212e68",
          "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": "qwkb9iy4ohi6d2lsxi3yxyl8h",
          "local-sessionid:dockerfile": "qwkb9iy4ohi6d2lsxi3yxyl8h",
          "local-sessionid:shared": "qwkb9iy4ohi6d2lsxi3yxyl8h"
        },
        "locals": [
          {
            "name": "context"
          },
          {
            "name": "dist"
          },
          {
            "name": "dockerfile"
          },
          {
            "name": "shared"
          }
        ]
      },
      "environment": {
        "platform": "linux/amd64"
      }
    }
  },
  "buildx.build.ref": "builder-f85b03ac-26b5-4e09-9504-288dde8ca645/builder-f85b03ac-26b5-4e09-9504-288dde8ca6450/cxgzm6f0o50eg89cc68gm5c0v",
  "containerimage.config.digest": "sha256:48f3ce6f83515a034ba53bcab3a974ba28de9fd66dbd9df5cb20b32e35aa8d70",
  "containerimage.descriptor": {
    "mediaType": "application/vnd.docker.distribution.manifest.v2+json",
    "digest": "sha256:1927ae68691ac06c2392a0b04ae6628c3d9241d8dd29a9f3bc787c35a55bbeb4",
    "size": 2075,
    "platform": {
      "architecture": "amd64",
      "os": "linux"
    }
  },
  "containerimage.digest": "sha256:1927ae68691ac06c2392a0b04ae6628c3d9241d8dd29a9f3bc787c35a55bbeb4",
  "image.name": "ghcr.io/graphql-hive/app:e345a1310373345bc0f1b9e74c8ed18def212e68-amd64,ghcr.io/graphql-hive/app:linkable_spec_lib-amd64"
},
"buildx.build.warnings": [
  {
    "vertex": "sha256:d9a3ce21e7d65628dc98df242361c9b3d2bd6fdcdb8863d37e6b350785483d88",
    "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:d9a3ce21e7d65628dc98df242361c9b3d2bd6fdcdb8863d37e6b350785483d88",
    "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:d9a3ce21e7d65628dc98df242361c9b3d2bd6fdcdb8863d37e6b350785483d88",
    "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:bd5c66f1d0f48576def83ef418f0fcbd3b2c357d9cb67aa6fdc8568aa0672e4d",
    "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:bd5c66f1d0f48576def83ef418f0fcbd3b2c357d9cb67aa6fdc8568aa0672e4d",
    "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
        }
      }
    ]
  },
  {
    "vertex": "sha256:d9a3ce21e7d65628dc98df242361c9b3d2bd6fdcdb8863d37e6b350785483d88",
    "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:d9a3ce21e7d65628dc98df242361c9b3d2bd6fdcdb8863d37e6b350785483d88",
    "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:d9a3ce21e7d65628dc98df242361c9b3d2bd6fdcdb8863d37e6b350785483d88",
    "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:bd5c66f1d0f48576def83ef418f0fcbd3b2c357d9cb67aa6fdc8568aa0672e4d",
    "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:d9a3ce21e7d65628dc98df242361c9b3d2bd6fdcdb8863d37e6b350785483d88",
    "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:d9a3ce21e7d65628dc98df242361c9b3d2bd6fdcdb8863d37e6b350785483d88",
    "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:bd5c66f1d0f48576def83ef418f0fcbd3b2c357d9cb67aa6fdc8568aa0672e4d",
    "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:bd5c66f1d0f48576def83ef418f0fcbd3b2c357d9cb67aa6fdc8568aa0672e4d",
    "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:bd5c66f1d0f48576def83ef418f0fcbd3b2c357d9cb67aa6fdc8568aa0672e4d",
    "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
        }
      }
    ]
  }
],
"composition-federation-2": {
  "buildx.build.provenance": {
    "buildType": "https://mobyproject.org/buildkit@v1",
    "materials": [
      {
        "uri": "pkg:docker/node@22.13.0-slim?platform=linux%2Famd64",
        "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": "e345a1310373345bc0f1b9e74c8ed18def212e68",
          "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": "qwkb9iy4ohi6d2lsxi3yxyl8h",
          "local-sessionid:dockerfile": "qwkb9iy4ohi6d2lsxi3yxyl8h",
          "local-sessionid:shared": "qwkb9iy4ohi6d2lsxi3yxyl8h"
        },
        "locals": [
          {
            "name": "context"
          },
          {
            "name": "dist"
          },
          {
            "name": "dockerfile"
          },
          {
            "name": "shared"
          }
        ]
      },
      "environment": {
        "platform": "linux/amd64"
      }
    }
  },
  "buildx.build.ref": "builder-f85b03ac-26b5-4e09-9504-288dde8ca645/builder-f85b03ac-26b5-4e09-9504-288dde8ca6450/qgft2h7gn46m6ojcezjdcnuxl",
  "containerimage.config.digest": "sha256:244a02c60abd10fed1b071f507e305722293e102de8c94b38ce6e7e7950a9d4d",
  "containerimage.descriptor": {
    "mediaType": "application/vnd.docker.distribution.manifest.v2+json",
    "digest": "sha256:00853d18cd97b63d62d6b1ddb856f6c42e2b399c27a2c19cdff201808a9d99e3",
    "size": 2075,
    "platform": {
      "architecture": "amd64",
      "os": "linux"
    }
  },
  "containerimage.digest": "sha256:00853d18cd97b63d62d6b1ddb856f6c42e2b399c27a2c19cdff201808a9d99e3",
  "image.name": "ghcr.io/graphql-hive/composition-federation-2:e345a1310373345bc0f1b9e74c8ed18def212e68-amd64,ghcr.io/graphql-hive/composition-federation-2:linkable_spec_lib-amd64"
},
"emails": {
  "buildx.build.provenance": {
    "buildType": "https://mobyproject.org/buildkit@v1",
    "materials": [
      {
        "uri": "pkg:docker/node@22.13.0-slim?platform=linux%2Famd64",
        "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": "e345a1310373345bc0f1b9e74c8ed18def212e68",
          "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": "qwkb9iy4ohi6d2lsxi3yxyl8h",
          "local-sessionid:dockerfile": "qwkb9iy4ohi6d2lsxi3yxyl8h",
          "local-sessionid:shared": "qwkb9iy4ohi6d2lsxi3yxyl8h"
        },
        "locals": [
          {
            "name": "context"
          },
          {
            "name": "dist"
          },
          {
            "name": "dockerfile"
          },
          {
            "name": "shared"
          }
        ]
      },
      "environment": {
        "platform": "linux/amd64"
      }
    }
  },
  "buildx.build.ref": "builder-f85b03ac-26b5-4e09-9504-288dde8ca645/builder-f85b03ac-26b5-4e09-9504-288dde8ca6450/gvbx66c43femnvnshgkxrgpxz",
  "containerimage.config.digest": "sha256:d044389cef617a6cb7eea7ddf5fa6c344363263ba62c37a120d632f60b3eb808",
  "containerimage.descriptor": {
    "mediaType": "application/vnd.docker.distribution.manifest.v2+json",
    "digest": "sha256:129a2d5363d1c3b782b3ae2d8bf205ae58321f9d262de48f09061a2747a785f1",
    "size": 2075,
    "platform": {
      "architecture": "amd64",
      "os": "linux"
    }
  },
  "containerimage.digest": "sha256:129a2d5363d1c3b782b3ae2d8bf205ae58321f9d262de48f09061a2747a785f1",
  "image.name": "ghcr.io/graphql-hive/emails:e345a1310373345bc0f1b9e74c8ed18def212e68-amd64,ghcr.io/graphql-hive/emails:linkable_spec_lib-amd64"
},
"policy": {
  "buildx.build.provenance": {
    "buildType": "https://mobyproject.org/buildkit@v1",
    "materials": [
      {
        "uri": "pkg:docker/node@22.13.0-slim?platform=linux%2Famd64",
        "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": "e345a1310373345bc0f1b9e74c8ed18def212e68",
          "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": "qwkb9iy4ohi6d2lsxi3yxyl8h",
          "local-sessionid:dockerfile": "qwkb9iy4ohi6d2lsxi3yxyl8h",
          "local-sessionid:shared": "qwkb9iy4ohi6d2lsxi3yxyl8h"
        },
        "locals": [
          {
            "name": "context"
          },
          {
            "name": "dist"
          },
          {
            "name": "dockerfile"
          },
          {
            "name": "shared"
          }
        ]
      },
      "environment": {
        "platform": "linux/amd64"
      }
    }
  },
  "buildx.build.ref": "builder-f85b03ac-26b5-4e09-9504-288dde8ca645/builder-f85b03ac-26b5-4e09-9504-288dde8ca6450/reg97oz2r6byiuhah4fmnhe0l",
  "containerimage.config.digest": "sha256:ec7a0468830efa0d61aae688552dd2a5b61f26caf3aa88aec9b26358e72a9956",
  "containerimage.descriptor": {
    "mediaType": "application/vnd.docker.distribution.manifest.v2+json",
    "digest": "sha256:a430d9ee76d5c8dd86b98a83d2a953231edc2ea6a6c5c2a370c1f3c7e141f0ff",
    "size": 2075,
    "platform": {
      "architecture": "amd64",
      "os": "linux"
    }
  },
  "containerimage.digest": "sha256:a430d9ee76d5c8dd86b98a83d2a953231edc2ea6a6c5c2a370c1f3c7e141f0ff",
  "image.name": "ghcr.io/graphql-hive/policy:e345a1310373345bc0f1b9e74c8ed18def212e68-amd64,ghcr.io/graphql-hive/policy:linkable_spec_lib-amd64"
},
"rate-limit": {
  "buildx.build.provenance": {
    "buildType": "https://mobyproject.org/buildkit@v1",
    "materials": [
      {
        "uri": "pkg:docker/node@22.13.0-slim?platform=linux%2Famd64",
        "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": "e345a1310373345bc0f1b9e74c8ed18def212e68",
          "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": "qwkb9iy4ohi6d2lsxi3yxyl8h",
          "local-sessionid:dockerfile": "qwkb9iy4ohi6d2lsxi3yxyl8h",
          "local-sessionid:shared": "qwkb9iy4ohi6d2lsxi3yxyl8h"
        },
        "locals": [
          {
            "name": "context"
          },
          {
            "name": "dist"
          },
          {
            "name": "dockerfile"
          },
          {
            "name": "shared"
          }
        ]
      },
      "environment": {
        "platform": "linux/amd64"
      }
    }
  },
  "buildx.build.ref": "builder-f85b03ac-26b5-4e09-9504-288dde8ca645/builder-f85b03ac-26b5-4e09-9504-288dde8ca6450/wg64egzlay0mqdvg772184lgs",
  "containerimage.config.digest": "sha256:e83218a39603ce4466e1032ae5ee6ab9f7a8b12d6c898e676796fbbbbf2d14c0",
  "containerimage.descriptor": {
    "mediaType": "application/vnd.docker.distribution.manifest.v2+json",
    "digest": "sha256:ea5918ad75bc235abbbdce7752c4152008f7036c8c23ac5b5946f4d563db19f5",
    "size": 2075,
    "platform": {
      "architecture": "amd64",
      "os": "linux"
    }
  },
  "containerimage.digest": "sha256:ea5918ad75bc235abbbdce7752c4152008f7036c8c23ac5b5946f4d563db19f5",
  "image.name": "ghcr.io/graphql-hive/rate-limit:e345a1310373345bc0f1b9e74c8ed18def212e68-amd64,ghcr.io/graphql-hive/rate-limit:linkable_spec_lib-amd64"
},
"schema": {
  "buildx.build.provenance": {
    "buildType": "https://mobyproject.org/buildkit@v1",
    "materials": [
      {
        "uri": "pkg:docker/node@22.13.0-slim?platform=linux%2Famd64",
        "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": "e345a1310373345bc0f1b9e74c8ed18def212e68",
          "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": "qwkb9iy4ohi6d2lsxi3yxyl8h",
          "local-sessionid:dockerfile": "qwkb9iy4ohi6d2lsxi3yxyl8h",
          "local-sessionid:shared": "qwkb9iy4ohi6d2lsxi3yxyl8h"
        },
        "locals": [
          {
            "name": "context"
          },
          {
            "name": "dist"
          },
          {
            "name": "dockerfile"
          },
          {
            "name": "shared"
          }
        ]
      },
      "environment": {
        "platform": "linux/amd64"
      }
    }
  },
  "buildx.build.ref": "builder-f85b03ac-26b5-4e09-9504-288dde8ca645/builder-f85b03ac-26b5-4e09-9504-288dde8ca6450/kp1bv7w9sp21fy2o3fbuar545",
  "containerimage.config.digest": "sha256:0b269507a2273170942e5b23f1d8156ce0da7673f075239912f7bf590ae90357",
  "containerimage.descriptor": {
    "mediaType": "application/vnd.docker.distribution.manifest.v2+json",
    "digest": "sha256:07288439dcf80b1527b49a919d99e0f095e747b351e5fb2db8c537468ff5fd56",
    "size": 2075,
    "platform": {
      "architecture": "amd64",
      "os": "linux"
    }
  },
  "containerimage.digest": "sha256:07288439dcf80b1527b49a919d99e0f095e747b351e5fb2db8c537468ff5fd56",
  "image.name": "ghcr.io/graphql-hive/schema:e345a1310373345bc0f1b9e74c8ed18def212e68-amd64,ghcr.io/graphql-hive/schema:linkable_spec_lib-amd64"
},
"server": {
  "buildx.build.provenance": {
    "buildType": "https://mobyproject.org/buildkit@v1",
    "materials": [
      {
        "uri": "pkg:docker/node@22.13.0-slim?platform=linux%2Famd64",
        "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": "e345a1310373345bc0f1b9e74c8ed18def212e68",
          "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": "qwkb9iy4ohi6d2lsxi3yxyl8h",
          "local-sessionid:dockerfile": "qwkb9iy4ohi6d2lsxi3yxyl8h",
          "local-sessionid:shared": "qwkb9iy4ohi6d2lsxi3yxyl8h"
        },
        "locals": [
          {
            "name": "context"
          },
          {
            "name": "dist"
          },
          {
            "name": "dockerfile"
          },
          {
            "name": "shared"
          }
        ]
      },
      "environment": {
        "platform": "linux/amd64"
      }
    }
  },
  "buildx.build.ref": "builder-f85b03ac-26b5-4e09-9504-288dde8ca645/builder-f85b03ac-26b5-4e09-9504-288dde8ca6450/wae9tyol5dk1eurqe083vbo9y",
  "containerimage.config.digest": "sha256:3a798d5b39b2d48edc964e5e947d33f9493d9cb1d8c90ac07b732ff7eb3f0178",
  "containerimage.descriptor": {
    "mediaType": "application/vnd.docker.distribution.manifest.v2+json",
    "digest": "sha256:4b1e896745080e5c842a2fa76809d7f07578fba297b859c6dd7c6dfbc8aeb05b",
    "size": 2076,
    "platform": {
      "architecture": "amd64",
      "os": "linux"
    }
  },
  "containerimage.digest": "sha256:4b1e896745080e5c842a2fa76809d7f07578fba297b859c6dd7c6dfbc8aeb05b",
  "image.name": "ghcr.io/graphql-hive/server:e345a1310373345bc0f1b9e74c8ed18def212e68-amd64,ghcr.io/graphql-hive/server:linkable_spec_lib-amd64"
},
"storage": {
  "buildx.build.provenance": {
    "buildType": "https://mobyproject.org/buildkit@v1",
    "materials": [
      {
        "uri": "pkg:docker/node@22.13.0-slim?platform=linux%2Famd64",
        "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": "e345a1310373345bc0f1b9e74c8ed18def212e68",
          "context:dist": "local:dist",
          "context:shared": "local:shared",
          "frontend.caps": "moby.buildkit.frontend.contexts+forward",
          "local-sessionid:context": "qwkb9iy4ohi6d2lsxi3yxyl8h",
          "local-sessionid:dockerfile": "qwkb9iy4ohi6d2lsxi3yxyl8h",
          "local-sessionid:shared": "qwkb9iy4ohi6d2lsxi3yxyl8h"
        },
        "locals": [
          {
            "name": "context"
          },
          {
            "name": "dist"
          },
          {
            "name": "dockerfile"
          },
          {
            "name": "shared"
          }
        ]
      },
      "environment": {
        "platform": "linux/amd64"
      }
    }
  },
  "buildx.build.ref": "builder-f85b03ac-26b5-4e09-9504-288dde8ca645/builder-f85b03ac-26b5-4e09-9504-288dde8ca6450/kpl67m4rj2myxjewu6kuom4vi",
  "containerimage.config.digest": "sha256:34c0340f85264b734459e9aaadf4a907d22eae065accbd5df890b5d0847d0c9f",
  "containerimage.descriptor": {
    "mediaType": "application/vnd.docker.distribution.manifest.v2+json",
    "digest": "sha256:56468b11067d914b6fda853004b1e4966ea3770834aea286c48c74a18e4319e5",
    "size": 2075,
    "platform": {
      "architecture": "amd64",
      "os": "linux"
    }
  },
  "containerimage.digest": "sha256:56468b11067d914b6fda853004b1e4966ea3770834aea286c48c74a18e4319e5",
  "image.name": "ghcr.io/graphql-hive/storage:e345a1310373345bc0f1b9e74c8ed18def212e68-amd64,ghcr.io/graphql-hive/storage:linkable_spec_lib-amd64"
},
"stripe-billing": {
  "buildx.build.provenance": {
    "buildType": "https://mobyproject.org/buildkit@v1",
    "materials": [
      {
        "uri": "pkg:docker/node@22.13.0-slim?platform=linux%2Famd64",
        "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": "e345a1310373345bc0f1b9e74c8ed18def212e68",
          "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": "qwkb9iy4ohi6d2lsxi3yxyl8h",
          "local-sessionid:dockerfile": "qwkb9iy4ohi6d2lsxi3yxyl8h",
          "local-sessionid:shared": "qwkb9iy4ohi6d2lsxi3yxyl8h"
        },
        "locals": [
          {
            "name": "context"
          },
          {
            "name": "dist"
          },
          {
            "name": "dockerfile"
          },
          {
            "name": "shared"
          }
        ]
      },
      "environment": {
        "platform": "linux/amd64"
      }
    }
  },
  "buildx.build.ref": "builder-f85b03ac-26b5-4e09-9504-288dde8ca645/builder-f85b03ac-26b5-4e09-9504-288dde8ca6450/yfdxylagvmt20nrfobsdbljgb",
  "containerimage.config.digest": "sha256:c349b598ebe204aa274b1566e4204a07c089a49164ca43121a5709b90c7b133f",
  "containerimage.descriptor": {
    "mediaType": "application/vnd.docker.distribution.manifest.v2+json",
    "digest": "sha256:3ee0f88bcb384b0c9146c9b59fec892625f2ea3d05629df0fd3399dd18b200cb",
    "size": 2075,
    "platform": {
      "architecture": "amd64",
      "os": "linux"
    }
  },
  "containerimage.digest": "sha256:3ee0f88bcb384b0c9146c9b59fec892625f2ea3d05629df0fd3399dd18b200cb",
  "image.name": "ghcr.io/graphql-hive/stripe-billing:e345a1310373345bc0f1b9e74c8ed18def212e68-amd64,ghcr.io/graphql-hive/stripe-billing:linkable_spec_lib-amd64"
},
"tokens": {
  "buildx.build.provenance": {
    "buildType": "https://mobyproject.org/buildkit@v1",
    "materials": [
      {
        "uri": "pkg:docker/node@22.13.0-slim?platform=linux%2Famd64",
        "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": "e345a1310373345bc0f1b9e74c8ed18def212e68",
          "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": "qwkb9iy4ohi6d2lsxi3yxyl8h",
          "local-sessionid:dockerfile": "qwkb9iy4ohi6d2lsxi3yxyl8h",
          "local-sessionid:shared": "qwkb9iy4ohi6d2lsxi3yxyl8h"
        },
        "locals": [
          {
            "name": "context"
          },
          {
            "name": "dist"
          },
          {
            "name": "dockerfile"
          },
          {
            "name": "shared"
          }
        ]
      },
      "environment": {
        "platform": "linux/amd64"
      }
    }
  },
  "buildx.build.ref": "builder-f85b03ac-26b5-4e09-9504-288dde8ca645/builder-f85b03ac-26b5-4e09-9504-288dde8ca6450/aeu6pox5jskv9btlup1ajpa2v",
  "containerimage.config.digest": "sha256:f6569ae12aef33a875297c0bf745924a1420da3ffbfd137eda3edee65ca3e1a8",
  "containerimage.descriptor": {
    "mediaType": "application/vnd.docker.distribution.manifest.v2+json",
    "digest": "sha256:3da573ef69654b77081454570d276eb51cfbc186ba1b6de510d7aa72840f81bd",
    "size": 2075,
    "platform": {
      "architecture": "amd64",
      "os": "linux"
    }
  },
  "containerimage.digest": "sha256:3da573ef69654b77081454570d276eb51cfbc186ba1b6de510d7aa72840f81bd",
  "image.name": "ghcr.io/graphql-hive/tokens:e345a1310373345bc0f1b9e74c8ed18def212e68-amd64,ghcr.io/graphql-hive/tokens:linkable_spec_lib-amd64"
},
"usage": {
  "buildx.build.provenance": {
    "buildType": "https://mobyproject.org/buildkit@v1",
    "materials": [
      {
        "uri": "pkg:docker/node@22.13.0-slim?platform=linux%2Famd64",
        "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": "e345a1310373345bc0f1b9e74c8ed18def212e68",
          "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": "qwkb9iy4ohi6d2lsxi3yxyl8h",
          "local-sessionid:dockerfile": "qwkb9iy4ohi6d2lsxi3yxyl8h",
          "local-sessionid:shared": "qwkb9iy4ohi6d2lsxi3yxyl8h"
        },
        "locals": [
          {
            "name": "context"
          },
          {
            "name": "dist"
          },
          {
            "name": "dockerfile"
          },
          {
            "name": "shared"
          }
        ]
      },
      "environment": {
        "platform": "linux/amd64"
      }
    }
  },
  "buildx.build.ref": "builder-f85b03ac-26b5-4e09-9504-288dde8ca645/builder-f85b03ac-26b5-4e09-9504-288dde8ca6450/vhj14wflbvksb2u8z61ypo3nt",
  "containerimage.config.digest": "sha256:1566a48292105b2430fe28679e47bf1ed9b6d2b84c4165b0e056ad709a0c0be8",
  "containerimage.descriptor": {
    "mediaType": "application/vnd.docker.distribution.manifest.v2+json",
    "digest": "sha256:cabbad0f4b075c70d3fe8c81c3d7b043562d0d394d691a75a71ee52e9cdde89a",
    "size": 2075,
    "platform": {
      "architecture": "amd64",
      "os": "linux"
    }
  },
  "containerimage.digest": "sha256:cabbad0f4b075c70d3fe8c81c3d7b043562d0d394d691a75a71ee52e9cdde89a",
  "image.name": "ghcr.io/graphql-hive/usage:e345a1310373345bc0f1b9e74c8ed18def212e68-amd64,ghcr.io/graphql-hive/usage:linkable_spec_lib-amd64"
},
"usage-estimator": {
  "buildx.build.provenance": {
    "buildType": "https://mobyproject.org/buildkit@v1",
    "materials": [
      {
        "uri": "pkg:docker/node@22.13.0-slim?platform=linux%2Famd64",
        "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": "e345a1310373345bc0f1b9e74c8ed18def212e68",
          "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": "qwkb9iy4ohi6d2lsxi3yxyl8h",
          "local-sessionid:dockerfile": "qwkb9iy4ohi6d2lsxi3yxyl8h",
          "local-sessionid:shared": "qwkb9iy4ohi6d2lsxi3yxyl8h"
        },
        "locals": [
          {
            "name": "context"
          },
          {
            "name": "dist"
          },
          {
            "name": "dockerfile"
          },
          {
            "name": "shared"
          }
        ]
      },
      "environment": {
        "platform": "linux/amd64"
      }
    }
  },
  "buildx.build.ref": "builder-f85b03ac-26b5-4e09-9504-288dde8ca645/builder-f85b03ac-26b5-4e09-9504-288dde8ca6450/v18xlxpgdqw1b9dqmkl4qw0wd",
  "containerimage.config.digest": "sha256:676213864c6eaab62b341d3971a52a8be906ab330c7800815776408caee2daf6",
  "containerimage.descriptor": {
    "mediaType": "application/vnd.docker.distribution.manifest.v2+json",
    "digest": "sha256:00ca9c8d5de47c6b04971a2eec37463a44827fb9ab9de365e498dc9decaf9a2e",
    "size": 2075,
    "platform": {
      "architecture": "amd64",
      "os": "linux"
    }
  },
  "containerimage.digest": "sha256:00ca9c8d5de47c6b04971a2eec37463a44827fb9ab9de365e498dc9decaf9a2e",
  "image.name": "ghcr.io/graphql-hive/usage-estimator:e345a1310373345bc0f1b9e74c8ed18def212e68-amd64,ghcr.io/graphql-hive/usage-estimator:linkable_spec_lib-amd64"
},
"usage-ingestor": {
  "buildx.build.provenance": {
    "buildType": "https://mobyproject.org/buildkit@v1",
    "materials": [
      {
        "uri": "pkg:docker/node@22.13.0-slim?platform=linux%2Famd64",
        "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": "e345a1310373345bc0f1b9e74c8ed18def212e68",
          "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": "qwkb9iy4ohi6d2lsxi3yxyl8h",
          "local-sessionid:dockerfile": "qwkb9iy4ohi6d2lsxi3yxyl8h",
          "local-sessionid:shared": "qwkb9iy4ohi6d2lsxi3yxyl8h"
        },
        "locals": [
          {
            "name": "context"
          },
          {
            "name": "dist"
          },
          {
            "name": "dockerfile"
          },
          {
            "name": "shared"
          }
        ]
      },
      "environment": {
        "platform": "linux/amd64"
      }
    }
  },
  "buildx.build.ref": "builder-f85b03ac-26b5-4e09-9504-288dde8ca645/builder-f85b03ac-26b5-4e09-9504-288dde8ca6450/v5g7fqwull5b79zhr9qr25pvh",
  "containerimage.config.digest": "sha256:1ffc0823eb0f1cc06c219e387d79de8c3cd4169acdb4fc878f6dfd1d0a03d2c1",
  "containerimage.descriptor": {
    "mediaType": "application/vnd.docker.distribution.manifest.v2+json",
    "digest": "sha256:c93d7cb9cd6911bf23e287aa8d99578faa4b5266b2ebd29c7557a8fc03ac5aa6",
    "size": 2075,
    "platform": {
      "architecture": "amd64",
      "os": "linux"
    }
  },
  "containerimage.digest": "sha256:c93d7cb9cd6911bf23e287aa8d99578faa4b5266b2ebd29c7557a8fc03ac5aa6",
  "image.name": "ghcr.io/graphql-hive/usage-ingestor:e345a1310373345bc0f1b9e74c8ed18def212e68-amd64,ghcr.io/graphql-hive/usage-ingestor:linkable_spec_lib-amd64"
},
"webhooks": {
  "buildx.build.provenance": {
    "buildType": "https://mobyproject.org/buildkit@v1",
    "materials": [
      {
        "uri": "pkg:docker/node@22.13.0-slim?platform=linux%2Famd64",
        "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": "e345a1310373345bc0f1b9e74c8ed18def212e68",
          "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": "qwkb9iy4ohi6d2lsxi3yxyl8h",
          "local-sessionid:dockerfile": "qwkb9iy4ohi6d2lsxi3yxyl8h",
          "local-sessionid:shared": "qwkb9iy4ohi6d2lsxi3yxyl8h"
        },
        "locals": [
          {
            "name": "context"
          },
          {
            "name": "dist"
          },
          {
            "name": "dockerfile"
          },
          {
            "name": "shared"
          }
        ]
      },
      "environment": {
        "platform": "linux/amd64"
      }
    }
  },
  "buildx.build.ref": "builder-f85b03ac-26b5-4e09-9504-288dde8ca645/builder-f85b03ac-26b5-4e09-9504-288dde8ca6450/kju8qgauts8vli2v4ceja13i4",
  "containerimage.config.digest": "sha256:65e677ff7e2a49d3186911fdd91886b8a913827b8266e19bf08fceeac8c4b87a",
  "containerimage.descriptor": {
    "mediaType": "application/vnd.docker.distribution.manifest.v2+json",
    "digest": "sha256:2dd9529224de9c72b116275dbf3ff643a0d56887cc0bd4649c8669d3a927334f",
    "size": 2075,
    "platform": {
      "architecture": "amd64",
      "os": "linux"
    }
  },
  "containerimage.digest": "sha256:2dd9529224de9c72b116275dbf3ff643a0d56887cc0bd4649c8669d3a927334f",
  "image.name": "ghcr.io/graphql-hive/webhooks:e345a1310373345bc0f1b9e74c8ed18def212e68-amd64,ghcr.io/graphql-hive/webhooks:linkable_spec_lib-amd64"
}
}

@jdolle jdolle enabled auto-merge (squash) February 4, 2025 06:57
@n1ru4l
Copy link
Contributor

n1ru4l commented Feb 4, 2025

Is supporting Federation 1 out of the scope of this?

E.g. we support @tag/schema contracts for Federation 1 and 2. This directive only addresses Federation 2 use-cases though. https://www.apollographql.com/docs/graphos/platform/schema-management/delivery/contracts/create#1-add-tags-to-subgraph-schemas

Thinking about this, I would like to see this library being used on something like packages/services/schema/src/lib/federation-tag-extraction.ts to better understand its real-world usage.

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 (2)
packages/libraries/federation-link-utils/README.md (2)

9-12: Correct Grammar in the Description

There is a minor grammatical error on line 9: the phrase "make a custom features" should be revised for correctness. Consider rephrasing it to "create custom features" or "make custom features" for clarity.

-This library can be used to make a custom features for GraphQL schemas backed by Federation's
+This library can be used to create custom features for GraphQL schemas backed by Federation's
🧰 Tools
🪛 LanguageTool

[grammar] ~9-~9: Do not use the singular ‘a’ before the plural noun ‘features’.
Context: ... --- This library can be used to make a custom features for GraphQL schemas backed by Federatio...

(VB_A_JJ_NNS)


66-68: Clarify Final Notes on LinkableSpec Implementation

The concluding explanation regarding LinkableSpec is helpful, but it could benefit from additional context. A brief note or a reference link explaining what it means to "always return a yoga plugin" would add clarity for users who might not be familiar with that convention.

📜 Review details

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

📥 Commits

Reviewing files that changed from the base of the PR and between a84e735 and b751f8b.

📒 Files selected for processing (1)
  • packages/libraries/federation-link-utils/README.md (1 hunks)
🧰 Additional context used
📓 Path-based instructions (1)
packages/libraries/federation-link-utils/README.md (1)

Pattern 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.

📓 Learnings (1)
packages/libraries/federation-link-utils/README.md (1)
Learnt from: jdolle
PR: graphql-hive/console#6452
File: packages/libraries/linkable-specs/src/__tests__/index.spec.ts:25-25
Timestamp: 2025-02-03T03:41:50.270Z
Learning: The @graphql-hive/linkable-specs package handles directive namespacing automatically through its resolveImportName function. Users should use base directive names (e.g., '@example') rather than fully qualified names (e.g., '@metadata_example') as the package manages the namespace transformation internally.
🪛 LanguageTool
packages/libraries/federation-link-utils/README.md

[grammar] ~9-~9: Do not use the singular ‘a’ before the plural noun ‘features’.
Context: ... --- This library can be used to make a custom features for GraphQL schemas backed by Federatio...

(VB_A_JJ_NNS)

⏰ Context from checks skipped due to timeout of 90000ms (8)
  • GitHub Check: test / unit
  • GitHub Check: typescript / typecheck
  • GitHub Check: static-analysis / analyze (typescript)
  • 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 (6)
packages/libraries/federation-link-utils/README.md (6)

1-2: Descriptive Title and Overview

The file title "GraphQL Hive - federation-link-utils" is clear and descriptive. Consider adding a brief tagline that summarizes the library’s purpose to further orient new users.


3-6: Clear Introduction Provided

The introduction effectively explains what GraphQL Hive offers and provides useful links for more information. Please ensure that the external links remain up-to-date as the project evolves.


7-8: Well-Placed Section Separator

The horizontal rule is appropriately used to separate the introductory content from the rest of the document.


13-18: Feature List is Clear and Concise

The features section succinctly lists the library’s core capabilities. In future iterations, you might consider providing additional details or examples for each feature to enhance the user’s understanding.


19-35: Usage Example for GraphQL Schema is Informative

The provided GraphQL schema example clearly demonstrates how to extend a schema using the @link directive. This example is valuable for users looking to grasp the practical application of the library.


36-64: Enhance Code Snippet Clarity with Required Imports

The TypeScript snippet effectively demonstrates how to create a LinkableSpec and utilize detectLinkedImplementations. However, to improve clarity for developers integrating this code, consider explicitly adding the necessary imports (e.g., visit, parse, DocumentNode, and StringValueNode) from the appropriate modules.

+// Ensure to import required dependencies from 'graphql'
+import { DocumentNode, StringValueNode, visit, parse } from 'graphql';

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 (3)
packages/libraries/federation-link-utils/src/index.ts (1)

12-32: Consider adding JSDoc documentation for the function.

While the function implementation looks good, adding JSDoc documentation would help users understand:

  • The purpose of the function
  • The expected format of input parameters
  • The structure of the return value
  • Example usage
+/**
+ * Detects and returns implementations from a list of supported specifications based on the links defined in the schema.
+ * @param typeDefs - The GraphQL schema as a DocumentNode
+ * @param supportedSpecs - Array of LinkableSpec instances to detect implementations for
+ * @returns Array of detected implementations
+ * @example
+ * ```ts
+ * const typeDefs = parse(sdl);
+ * const specs = [new LinkableSpec(...)];
+ * const implementations = detectLinkedImplementations(typeDefs, specs);
+ * ```
+ */
 export function detectLinkedImplementations<T>(
   typeDefs: DocumentNode,
   supportedSpecs: LinkableSpec<T>[],
 ): T[] {
packages/libraries/federation-link-utils/src/linkable-spec.ts (1)

50-53: Consider using a structured logging solution.

Instead of using console.warn directly, consider using a structured logging solution that can be configured based on the environment and allows for better error tracking.

-      console.warn(
-        `Cannot apply @link due to unsupported version found for "${this.identity}". ` +
-          `Available versions: ${this.sortedVersionKeys.join(', ')} and any version these are compatible compatible with.`,
-      );
+      const message = `Cannot apply @link due to unsupported version found for "${this.identity}". ` +
+        `Available versions: ${this.sortedVersionKeys.join(', ')} and any version these are compatible with.`;
+      this.logger?.warn?.(message) ?? console.warn(message);

Also, there's a typo in the message: "compatible" is repeated twice.

packages/libraries/federation-link-utils/README.md (1)

40-76: Enhance the TypeScript example.

The example code could be improved in several ways:

  1. Add type annotations for the sdl variable
  2. Show error handling for the apply function
  3. Document the expected shape of the return value
 // specs.ts
 import {
   detectLinkedImplementations,
   FEDERATION_V1,
-  LinkableSpec
+  LinkableSpec,
+  DocumentNode
 } from '@graphql-hive/federation-link-utils'

+// Define the shape of your implementation
+type ExampleImpl = Record<string, string>;
+
+// Your SDL as a string
+const sdl = `...`;
+
 const exampleSpec = new LinkableSpec('https://specs.graphql-hive.com/example', {
   [FEDERATION_V1]: resolveImportName => (typeDefs: DocumentNode) => {
     // option to support federation 1 schemas. Be extremely cautious here because versioning
     // cannot be done safely.
   },
   'v0.1': resolveImportName => (typeDefs: DocumentNode) => {
     const examples: Record<string, string> = {}
     // ... rest of the implementation
     return examples
   }
 })
 const typeDefs = parse(sdl)
 const linkedSpecs = detectLinkedImplementations(typeDefs, [exampleSpec])
-const result = linkedSpecs.map(apply => apply(typeDefs))
+const result = linkedSpecs.map(apply => {
+  try {
+    return apply(typeDefs) as ExampleImpl;
+  } catch (error) {
+    console.error('Failed to apply spec:', error);
+    return {};
+  }
+});
📜 Review details

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

📥 Commits

Reviewing files that changed from the base of the PR and between 484e969 and 1972b22.

📒 Files selected for processing (5)
  • packages/libraries/federation-link-utils/README.md (1 hunks)
  • packages/libraries/federation-link-utils/src/__tests__/index.spec.ts (1 hunks)
  • packages/libraries/federation-link-utils/src/index.ts (1 hunks)
  • packages/libraries/federation-link-utils/src/link.ts (1 hunks)
  • packages/libraries/federation-link-utils/src/linkable-spec.ts (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (2)
  • packages/libraries/federation-link-utils/src/tests/index.spec.ts
  • packages/libraries/federation-link-utils/src/link.ts
🧰 Additional context used
📓 Path-based instructions (3)
packages/libraries/federation-link-utils/src/linkable-spec.ts (1)

Pattern 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 (1)

Pattern 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/README.md (1)

Pattern 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.

📓 Learnings (1)
packages/libraries/federation-link-utils/README.md (1)
Learnt from: jdolle
PR: graphql-hive/console#6452
File: packages/libraries/linkable-specs/src/__tests__/index.spec.ts:25-25
Timestamp: 2025-02-03T03:41:50.270Z
Learning: The @graphql-hive/linkable-specs package handles directive namespacing automatically through its resolveImportName function. Users should use base directive names (e.g., '@example') rather than fully qualified names (e.g., '@metadata_example') as the package manages the namespace transformation internally.
🪛 LanguageTool
packages/libraries/federation-link-utils/README.md

[grammar] ~9-~9: Do not use the singular ‘a’ before the plural noun ‘features’.
Context: ... --- This library can be used to make a custom features for GraphQL schemas backed by Federatio...

(VB_A_JJ_NNS)

⏰ Context from checks skipped due to timeout of 90000ms (8)
  • GitHub Check: alpha / publish_docker / Build CLI Docker Image
  • GitHub Check: static-analysis / analyze (typescript)
  • GitHub Check: typescript / typecheck
  • 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/libraries/federation-link-utils/src/index.ts (1)

5-8: LGTM! Clean and well-organized exports.

The exports are properly organized and provide a clear public API for the library.

packages/libraries/federation-link-utils/README.md (1)

9-11: Fix grammar in library description.

There is a grammatical issue in the description.

-This library can be used to make a custom features for GraphQL schemas backed by Federation's
+This library can be used to create custom features for GraphQL schemas backed by Federation's
🧰 Tools
🪛 LanguageTool

[grammar] ~9-~9: Do not use the singular ‘a’ before the plural noun ‘features’.
Context: ... --- This library can be used to make a custom features for GraphQL schemas backed by Federatio...

(VB_A_JJ_NNS)

@jdolle jdolle changed the title feature: add linkable-spec utility lib feature: add federation-link-util library Feb 5, 2025
@jasonkuhrt jasonkuhrt self-requested a review February 5, 2025 14:05
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

♻️ Duplicate comments (1)
packages/libraries/federation-link-utils/src/linkable-spec.ts (1)

23-41: ⚠️ Potential issue

Add validation for version numbers.

While the code handles invalid version formats gracefully, it lacks explicit validation for version numbers. This could lead to unexpected behavior with invalid version strings.

Add validation before sorting:

 constructor(
   public readonly identity: string,
   public readonly versions: Versioned<T>,
 ) {
   // sort the versions in descending order for quicker lookups
   this.sortedVersionKeys = Object.keys(versions)
     .map(
       (
         key,
       ): {
         key: string;
         split?: number[];
       } => {
+        const match = key.match(VERSION_KEY_MATCH);
+        if (match) {
+          const [major, minor] = match.slice(1).map(Number);
+          if (isNaN(major) || isNaN(minor)) {
+            console.warn(`Invalid version format: ${key}`);
+            return { key };
+          }
+          return { key, split: [major, minor] };
+        }
         return { key };
-      }): { key, split: key.match(VERSION_KEY_MATCH)?.map(Number).slice(1) }),
     })
     .filter((v): v is Required<typeof v> => v.split !== undefined)
     .sort(({ split: [aMajor, aMinor] }, { split: [bMajor, bMinor] }) => {
       return bMajor !== aMajor ? bMajor - aMajor : bMinor - aMinor;
     });
 }
🧹 Nitpick comments (7)
packages/libraries/federation-link-utils/src/__tests__/linkable-spec.spec.ts (2)

12-21: Enhance test coverage with additional version combinations.

The test verifies basic version compatibility, but could be strengthened by adding test cases for:

  • Major version compatibility (e.g., v2.x vs v1.x)
  • Edge cases (e.g., highest compatible version when multiple versions match)
  • Invalid version formats

23-32: Add assertions for warning messages.

The test verifies null version handling, but could be improved by:

  • Adding test cases for warning messages when unsupported versions are encountered
  • Verifying that NULL_VERSION is used as a last resort
packages/libraries/federation-link-utils/src/linkable-spec.ts (1)

74-77: Improve warning message clarity.

The warning message about available versions could be clearer.

Apply this diff to improve the message:

-        `Cannot apply @link due to unsupported version found for "${this.identity}". ` +
-          `Available versions: ${this.sortedVersionKeys.map(v => `${v.split.join('.')}`).join(', ')} and any version these are compatible compatible with.`,
+        `Cannot apply @link due to unsupported version found for "${this.identity}". ` +
+          `Available versions: ${this.sortedVersionKeys.map(v => `v${v.split.join('.')}`).join(', ')}. ` +
+          `Note: Each version is compatible with higher minor versions within the same major version.`,
packages/libraries/federation-link-utils/src/__tests__/index.spec.ts (4)

11-53: Refactor metadata collection logic to reduce duplication.

The directive argument extraction logic is repeated multiple times. Consider extracting this into a helper function.

+'  const getDirectiveArgValue = (directive: any, argName: string) => {
+    return (
+      directive.arguments?.find(a => a.name.value === argName)?.value as
+        StringValueNode
+        | undefined
+    )?.value ?? '??';
+  };
+
   'v0.1': resolveImportName => (typeDefs: DocumentNode) => {
     const collectedMeta: Record<string, Record<string, string>> = {};
     const metaName = resolveImportName('@meta');
     const exampleName = resolveImportName('@example');
     visit(typeDefs, {
       FieldDefinition: node => {
         let metaData: Record<string, string> = {};
         const fieldName = node.name.value;
         const meta = node.directives?.find(d => d.name.value === metaName);
         if (meta) {
-          metaData['name'] =
-            (
-              meta.arguments?.find(a => a.name.value === 'name')?.value as
-                | StringValueNode
-                | undefined
-            )?.value ?? '??';
-          metaData['content'] =
-            (
-              meta.arguments?.find(a => a.name.value === 'content')?.value as
-                | StringValueNode
-                | undefined
-            )?.value ?? '??';
+          metaData['name'] = getDirectiveArgValue(meta, 'name');
+          metaData['content'] = getDirectiveArgValue(meta, 'content');
         }

         const example = node.directives?.find(d => d.name.value === exampleName);
         if (example) {
-          metaData['eg'] =
-            (
-              example.arguments?.find(a => a.name.value === 'eg')?.value as
-                | StringValueNode
-                | undefined
-            )?.value ?? '??';
+          metaData['eg'] = getDirectiveArgValue(example, 'eg');
         }

61-82: Enhance test coverage with explicit assertions.

While snapshot testing is useful, consider adding explicit assertions to verify:

  1. The presence of both named and namespaced directives in the collected metadata
  2. The structure of the collected metadata object
   const typeDefs = parse(sdl);
   const linked = detectLinkedImplementations(typeDefs, [metaSpec]);
-  expect(linked.map(link => link(typeDefs))).toMatchInlineSnapshot(`
-    [
-      running on v0.1.
-    Found metadata: {"ping":{"name":"owner","content":"hive-console-team"},"pong":{"eg":"1...2...3... Pong"}}},
-    ]
-  `);
+  const result = linked.map(link => link(typeDefs));
+  
+  // Snapshot for regression testing
+  expect(result).toMatchInlineSnapshot(`
+    [
+      running on v0.1.
+    Found metadata: {"ping":{"name":"owner","content":"hive-console-team"},"pong":{"eg":"1...2...3... Pong"}}},
+    ]
+  `);
+  
+  // Explicit assertions
+  const metadata = JSON.parse(result[0].split('Found metadata: ')[1]);
+  expect(metadata).toHaveProperty('ping.name', 'owner');
+  expect(metadata).toHaveProperty('ping.content', 'hive-console-team');
+  expect(metadata).toHaveProperty('pong.eg', '1...2...3... Pong');

84-99: Add test description and assertions for Federation v1 fallback.

The test could be more explicit about the expected behavior when the link directive is missing.

-test('LinkableSpec and detectLinkedImplementations can be used to apply linked schema in schemas that are missing the link directive', () => {
+test('falls back to Federation v1 implementation when @link directive is missing', () => {
   const sdl = `
     directive @meta(name: String!, content: String!) on SCHEMA | FIELD

     type Query {
       ping: String @meta(name: "owner", content: "hive-console-team")
     }
   `;
   const typeDefs = parse(sdl);
   const linked = detectLinkedImplementations(typeDefs, [metaSpec]);
-  expect(linked.map(link => link(typeDefs))).toMatchInlineSnapshot(`
-    [
-      Missing federation 2 support,
-    ]
-  `);
+  const result = linked.map(link => link(typeDefs));
+  
+  // Verify Federation v1 fallback
+  expect(result).toHaveLength(1);
+  expect(result[0]).toBe('Missing federation 2 support');
+  
+  // Keep snapshot for regression testing
+  expect(result).toMatchInlineSnapshot(`
+    [
+      Missing federation 2 support,
+    ]
+  `);

101-119: Enhance version underscore test coverage.

Consider adding more test cases to thoroughly verify version handling:

  1. Different underscore patterns
  2. Version comparison logic
+test('LinkableSpec handles various version formats correctly', () => {
+  const testCases = [
+    {
+      version: 'v0_3',
+      expected: 'Version 0.3 used',
+    },
+    {
+      version: 'v0_3_1',
+      expected: 'Missing federation 2 support', // Falls back to v1 for unknown versions
+    },
+  ];
+
+  testCases.forEach(({ version, expected }) => {
+    const sdl = `
+      directive @meta(name: String!, content: String!) on SCHEMA | FIELD
+      extend schema
+        @link(url: "https://specs.apollo.dev/federation/v2.3")
+        @link(url: "https://specs.graphql-hive.com/metadata/${version}", import: ["@meta"])
+
+      type Query {
+        ping: String @meta(name: "owner", content: "hive-console-team")
+      }
+    `;
+    const typeDefs = parse(sdl);
+    const linked = detectLinkedImplementations(typeDefs, [metaSpec]);
+    expect(linked.map(link => link(typeDefs))[0]).toBe(expected);
+  });
+});
📜 Review details

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

📥 Commits

Reviewing files that changed from the base of the PR and between 5d9edbd and f7b374e.

📒 Files selected for processing (6)
  • packages/libraries/federation-link-utils/README.md (1 hunks)
  • packages/libraries/federation-link-utils/src/__tests__/index.spec.ts (1 hunks)
  • packages/libraries/federation-link-utils/src/__tests__/linkable-spec.spec.ts (1 hunks)
  • packages/libraries/federation-link-utils/src/link-url.ts (1 hunks)
  • packages/libraries/federation-link-utils/src/link.ts (1 hunks)
  • packages/libraries/federation-link-utils/src/linkable-spec.ts (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (3)
  • packages/libraries/federation-link-utils/README.md
  • packages/libraries/federation-link-utils/src/link-url.ts
  • packages/libraries/federation-link-utils/src/link.ts
🧰 Additional context used
📓 Path-based instructions (1)
`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/__tests__/index.spec.ts
  • packages/libraries/federation-link-utils/src/__tests__/linkable-spec.spec.ts
  • packages/libraries/federation-link-utils/src/linkable-spec.ts
📓 Learnings (2)
packages/libraries/federation-link-utils/src/__tests__/index.spec.ts (2)
Learnt from: jdolle
PR: graphql-hive/console#6452
File: packages/libraries/federation-link-utils/src/__tests__/index.spec.ts:6-7
Timestamp: 2025-02-03T23:57:17.342Z
Learning: The federation-link-utils library supports two styles of directive imports:
1. Named imports (e.g., `@meta`)
2. Namespaced imports (e.g., `@metadata__example`)
Both styles can be used together in the same schema.
Learnt from: jdolle
PR: graphql-hive/console#6452
File: packages/libraries/linkable-specs/src/__tests__/index.spec.ts:25-25
Timestamp: 2025-02-03T03:41:50.270Z
Learning: The @graphql-hive/linkable-specs package handles directive namespacing automatically through its resolveImportName function. Users should use base directive names (e.g., '@example') rather than fully qualified names (e.g., '@metadata_example') as the package manages the namespace transformation internally.
packages/libraries/federation-link-utils/src/__tests__/linkable-spec.spec.ts (1)
Learnt from: jdolle
PR: graphql-hive/console#6452
File: packages/libraries/linkable-specs/src/__tests__/index.spec.ts:25-25
Timestamp: 2025-02-03T03:41:50.270Z
Learning: The @graphql-hive/linkable-specs package handles directive namespacing automatically through its resolveImportName function. Users should use base directive names (e.g., '@example') rather than fully qualified names (e.g., '@metadata_example') as the package manages the namespace transformation internally.
🔇 Additional comments (2)
packages/libraries/federation-link-utils/src/linkable-spec.ts (2)

3-12: LGTM! Well-documented constants and flexible version pattern.

The version pattern and constants are well-defined and documented. The regex pattern is flexible enough to handle both dot and underscore separators while maintaining reasonable limits on version numbers.


14-18: LGTM! Well-structured type definition.

The type definition is well-structured and correctly handles all version cases including string versions, federation v1, and null version.

@jdolle jdolle force-pushed the linkable-spec-lib branch from 9c2e70e to 4d50901 Compare February 5, 2025 18:52
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 (6)
packages/libraries/federation-link-utils/src/link-import.ts (2)

9-11: Add JSDoc documentation for better code maintainability.

Consider adding JSDoc documentation to explain the format of the returned string and provide examples.

+  /**
+   * Returns a string representation of the federated link import.
+   * @returns {string} For imports with alias: `{ name: "name", as: "alias" }`
+   *                   For imports without alias: `"name"`
+   * @example
+   * new FederatedLinkImport("users", null).toString() // returns `"users"`
+   * new FederatedLinkImport("users", "u").toString() // returns `{ name: "users", as: "u" }`
+   */
   public toString(): string {

13-47: Add input validation and improve code structure.

The method handles the parsing well but could benefit from some improvements:

  1. Add validation for empty name
  2. Use early returns to reduce nesting
  3. Add JSDoc documentation
+  /**
+   * Parses a GraphQL AST node to create FederatedLinkImport instances.
+   * @param {ConstValueNode} node - The AST node containing @link import declarations
+   * @returns {FederatedLinkImport[]} Array of parsed imports
+   * @throws {Error} If the node is not a list or contains invalid values
+   */
   static fromTypedefs(node: ConstValueNode): FederatedLinkImport[] {
     if (node.kind !== Kind.LIST) {
+      throw new Error(`Expected a list of @link imports but got "${node.kind}"`);
+    }
+
+    return node.values.map(v => {
+      if (v.kind === Kind.STRING) {
+        if (!v.value.trim()) {
+          throw new Error('Empty string is not allowed as @link import name');
+        }
+        return new FederatedLinkImport(v.value, null);
+      }
+
+      if (v.kind !== Kind.OBJECT) {
+        throw new Error(`Unexpected value kind "${v.kind}" in @link import declaration`);
+      }
+
+      let name = '';
+      let as: string | null = null;
+
+      v.fields.forEach(f => {
+        if (f.name.value === 'name') {
+          if (f.value.kind !== Kind.STRING) {
+            throw new Error(
+              `Expected string value for @link "name" field but got "${f.value.kind}"`,
+            );
+          }
+          name = f.value.value;
+        } else if (f.name.value === 'as') {
+          if (f.value.kind !== Kind.STRING) {
+            throw new Error(
+              `Expected string value for @link "as" field but got "${f.value.kind}"`,
+            );
+          }
+          as = f.value.value;
+        }
+      });
+
+      if (!name.trim()) {
+        throw new Error('Empty string is not allowed as @link import name');
+      }
+
+      return new FederatedLinkImport(name, as);
+    });
-    if (node.kind == Kind.LIST) {
-      const imports = node.values.map(v => {
-        if (v.kind === Kind.STRING) {
-          return new FederatedLinkImport(v.value, null);
-        }
-        if (v.kind === Kind.OBJECT) {
-          let name: string = '';
-          let as: string | null = null;
-
-          v.fields.forEach(f => {
-            if (f.name.value === 'name') {
-              if (f.value.kind !== Kind.STRING) {
-                throw new Error(
-                  `Expected string value for @link "name" field but got "${f.value.kind}"`,
-                );
-              }
-              name = f.value.value;
-            } else if (f.name.value === 'as') {
-              if (f.value.kind !== Kind.STRING) {
-                throw new Error(
-                  `Expected string value for @link "as" field but got "${f.value.kind}"`,
-                );
-              }
-              as = f.value.value;
-            }
-          });
-          return new FederatedLinkImport(name, as);
-        }
-        throw new Error(`Unexpected value kind "${v.kind}" in @link import declaration`);
-      });
-      return imports;
-    }
-    throw new Error(`Expected a list of @link imports but got "${node.kind}"`);
   }
packages/libraries/federation-link-utils/src/link-url.ts (2)

1-10: Consider verifying the match array length explicitly.

Currently, the check if (versionParts?.length) only ensures a non-empty match array. Consider explicitly verifying that versionParts has at least 3 elements (the full match plus the two capture groups) for extra safety.


53-79: Consider unifying overloads for maintainability.

The overload approach works but can be simplified with union types or a single function signature. Also, verify if identity checks should apply for the null version case, since they're currently omitted.

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

10-10: Optionally export this constant if it has broader use.

Defining FEDERATION_IDENTITY here is fine. If other modules need it, consider re-exporting it to avoid duplication.


12-32: Implementation is straightforward.

The logic cleanly handles the Federation 1 fallback vs. Federation 2 detection. Ensure that ignoring possible null returns in detectImplementation is acceptable; currently the code filters out only undefined values.

📜 Review details

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

📥 Commits

Reviewing files that changed from the base of the PR and between 9c2e70e and 4d50901.

⛔ Files ignored due to path filters (1)
  • pnpm-lock.yaml is excluded by !**/pnpm-lock.yaml
📒 Files selected for processing (14)
  • packages/libraries/federation-link-utils/README.md (1 hunks)
  • packages/libraries/federation-link-utils/package.json (1 hunks)
  • packages/libraries/federation-link-utils/src/__tests__/index.spec.ts (1 hunks)
  • packages/libraries/federation-link-utils/src/__tests__/link-url.spec.ts (1 hunks)
  • packages/libraries/federation-link-utils/src/__tests__/link.spec.ts (1 hunks)
  • packages/libraries/federation-link-utils/src/__tests__/linkable-spec.spec.ts (1 hunks)
  • packages/libraries/federation-link-utils/src/index.ts (1 hunks)
  • packages/libraries/federation-link-utils/src/link-import.ts (1 hunks)
  • packages/libraries/federation-link-utils/src/link-url.ts (1 hunks)
  • packages/libraries/federation-link-utils/src/link.ts (1 hunks)
  • packages/libraries/federation-link-utils/src/linkable-spec.ts (1 hunks)
  • packages/libraries/federation-link-utils/src/version.ts (1 hunks)
  • packages/libraries/federation-link-utils/tsconfig.json (1 hunks)
  • tsconfig.json (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (11)
  • packages/libraries/federation-link-utils/src/version.ts
  • packages/libraries/federation-link-utils/tsconfig.json
  • packages/libraries/federation-link-utils/src/tests/linkable-spec.spec.ts
  • packages/libraries/federation-link-utils/package.json
  • packages/libraries/federation-link-utils/README.md
  • packages/libraries/federation-link-utils/src/tests/link-url.spec.ts
  • packages/libraries/federation-link-utils/src/tests/link.spec.ts
  • packages/libraries/federation-link-utils/src/tests/index.spec.ts
  • tsconfig.json
  • packages/libraries/federation-link-utils/src/linkable-spec.ts
  • packages/libraries/federation-link-utils/src/link.ts
🧰 Additional context used
📓 Path-based instructions (1)
`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-import.ts
  • packages/libraries/federation-link-utils/src/index.ts
  • packages/libraries/federation-link-utils/src/link-url.ts
⏰ Context from checks skipped due to timeout of 90000ms (12)
  • GitHub Check: alpha / npm / snapshot
  • GitHub Check: test / unit
  • GitHub Check: typescript / typecheck
  • GitHub Check: static-analysis / analyze (typescript)
  • GitHub Check: static-analysis / analyze (javascript)
  • GitHub Check: code-style / eslint-and-prettier
  • GitHub Check: db-types / diff
  • GitHub Check: build / dockerize (linux/arm64)
  • GitHub Check: build / dockerize (linux/amd64)
  • GitHub Check: test / db-migrations
  • GitHub Check: alpha / cli-artifacts
  • GitHub Check: SonarCloud
🔇 Additional comments (5)
packages/libraries/federation-link-utils/src/link-import.ts (1)

1-7: LGTM! Well-structured class declaration.

The class is well-designed with appropriate types and clear purpose.

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

16-34: Constructor logic is clear and well-structured.

The constructor correctly sets the major and minor fields using the parsed version tuple. The JSDoc above is also clear in describing the class functionality.


35-51: Confirm ignoring query parameters and fragments is intentional.

At the moment, any query parameters or fragments in the URL (?foo=bar, #baz) are stripped away. Verify that this behavior meets the intended requirements.


81-89: Pre-1.0 version compatibility logic looks correct.

Requiring an exact minor match for major version 0 matches common semver practices. This approach will help avoid subtle breaks in 0.x releases.

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

1-9: Neat organization of imports and exports.

Exporting modules from a single entry point helps maintain clarity and discoverability. This looks good.

@jdolle jdolle force-pushed the linkable-spec-lib branch from 4d50901 to 379ba8e Compare February 6, 2025 18:23
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/libraries/federation-link-utils/src/link-url.ts (3)

21-33: Constructor validation
The constructor’s straightforward approach of parsing the version, then storing major/minor is clear. However, if invalid versions are common, consider providing warnings or throwing an error instead of silently setting them to -1.


35-51: Consider robust URL parsing
This static method neatly parses and segments the URL. If you anticipate invalid or protocol-relative URLs, consider handling the potential exceptions that may arise from the URL constructor.


53-80: Consolidate overloaded supports method
While overloading is valid, this method might benefit from a helper for each type of argument to reduce complexity and improve readability.

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

66-79: Refine unsupported version handling
Logging a warning on unsupported versions is helpful for debugging. As an enhancement, consider throwing a typed error or returning metadata about the mismatch instead of just using console.warn.

📜 Review details

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

📥 Commits

Reviewing files that changed from the base of the PR and between 4d50901 and 379ba8e.

⛔ Files ignored due to path filters (1)
  • pnpm-lock.yaml is excluded by !**/pnpm-lock.yaml
📒 Files selected for processing (14)
  • packages/libraries/federation-link-utils/README.md (1 hunks)
  • packages/libraries/federation-link-utils/package.json (1 hunks)
  • packages/libraries/federation-link-utils/src/__tests__/index.spec.ts (1 hunks)
  • packages/libraries/federation-link-utils/src/__tests__/link-url.spec.ts (1 hunks)
  • packages/libraries/federation-link-utils/src/__tests__/link.spec.ts (1 hunks)
  • packages/libraries/federation-link-utils/src/__tests__/linkable-spec.spec.ts (1 hunks)
  • packages/libraries/federation-link-utils/src/index.ts (1 hunks)
  • packages/libraries/federation-link-utils/src/link-import.ts (1 hunks)
  • packages/libraries/federation-link-utils/src/link-url.ts (1 hunks)
  • packages/libraries/federation-link-utils/src/link.ts (1 hunks)
  • packages/libraries/federation-link-utils/src/linkable-spec.ts (1 hunks)
  • packages/libraries/federation-link-utils/src/version.ts (1 hunks)
  • packages/libraries/federation-link-utils/tsconfig.json (1 hunks)
  • tsconfig.json (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (11)
  • packages/libraries/federation-link-utils/src/version.ts
  • packages/libraries/federation-link-utils/src/tests/link-url.spec.ts
  • packages/libraries/federation-link-utils/package.json
  • packages/libraries/federation-link-utils/src/tests/index.spec.ts
  • tsconfig.json
  • packages/libraries/federation-link-utils/README.md
  • packages/libraries/federation-link-utils/src/link-import.ts
  • packages/libraries/federation-link-utils/tsconfig.json
  • packages/libraries/federation-link-utils/src/tests/link.spec.ts
  • packages/libraries/federation-link-utils/src/tests/linkable-spec.spec.ts
  • packages/libraries/federation-link-utils/src/link.ts
🧰 Additional context used
📓 Path-based instructions (1)
`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/linkable-spec.ts
  • packages/libraries/federation-link-utils/src/link-url.ts
⏰ Context from checks skipped due to timeout of 90000ms (8)
  • GitHub Check: alpha / cli-artifacts
  • GitHub Check: test / unit
  • GitHub Check: typescript / typecheck
  • GitHub Check: static-analysis / analyze (typescript)
  • GitHub Check: build / dockerize (linux/arm64)
  • GitHub Check: static-analysis / analyze (javascript)
  • GitHub Check: build / dockerize (linux/amd64)
  • GitHub Check: SonarCloud
🔇 Additional comments (9)
packages/libraries/federation-link-utils/src/link-url.ts (3)

1-10: Confirm the intended version range
The regex and logic extract only major and minor from a “v.” format, defaulting to [-1, -1] if no match is found. Ensure this covers all intended scenarios (e.g., ignoring patch versions and handling uppercase or extra segments) and verify that returning [-1, -1] is the correct fallback.


16-20: Visibility and naming for major/minor
Declaring these version components as private fields is good for encapsulation. The naming is consistent and makes the code self-explanatory.


81-89: Clear version compatibility check
The logic in “isCompatibleVersion” is concise and well-structured for your major-minor usage, especially handling major===0 as an exact match for the minor version. No additional suggestions here.

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

1-9: Concise re-export pattern
The import and export statements are neatly organized, making the various utilities discoverable from a single entry point.


12-32: Ensure partial federation usage is intentional
This function toggles between Federation V1 and V2 logic based on the presence of Federation V2 links. If partial or mixed usage of Federation versions is ever expected, consider clarifying or handling that edge case.

packages/libraries/federation-link-utils/src/linkable-spec.ts (4)

1-19: Symbols and typed definitions
Defining FEDERATION_V1, NULL_VERSION, and Versioned clarifies important fallback behavior and indicates special-case handling.


20-41: Repeat: Validate version strings before sorting
Similar to past feedback, the sorting logic assumes strictly valid “v.” versions, and this could lead to unexpected behavior if invalid strings slip in.


43-60: Highest matching version retrieval
“detectLinkVersion” scans for the largest compatible version. The logic is logically sound for descending-sorted version keys and graceful fallback to null if the link supports no known version.


62-64: Straightforward link lookup
“findLinkByIdentity” is concise and effectively retrieves the correct link instance.

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 (8)
packages/libraries/federation-link-utils/src/linkable-spec.ts (2)

3-3: Consider tightening the version regex pattern.

The current regex pattern v(\d{1,3})[\._](\d{1,4})/i is quite permissive:

  • Allows up to 3 digits for major version (e.g., v999)
  • Allows up to 4 digits for minor version (e.g., v1.9999)
  • Accepts both dot and underscore as separators
  • Is case-insensitive

This could potentially allow invalid or unconventional version formats.

-const VERSION_KEY_MATCH = /v(\d{1,3})[\._](\d{1,4})/i;
+const VERSION_KEY_MATCH = /^v(\d{1,2})\.(\d{1,3})$/;

76-79: Improve error message readability.

The error message construction can be improved for better readability and to fix the repeated word "compatible".

-      throw new Error(
-        `Cannot apply @link due to unsupported version found for "${maybeLink}". ` +
-          `Available versions: ${this.sortedVersionKeys.map(v => `${v.split.join('.')}`).join(', ')} and any version these are compatible compatible with. Try downgrading the link version to match these versions.`,
-      );
+      const availableVersions = this.sortedVersionKeys
+        .map(v => `${v.split.join('.')}`)
+        .join(', ');
+      throw new Error(
+        `Cannot apply @link due to unsupported version found for "${maybeLink}". ` +
+        `Available versions: ${availableVersions} and any version these are compatible with. ` +
+        `Try downgrading the link version to match these versions.`
+      );
packages/libraries/federation-link-utils/tests/link.spec.ts (1)

13-37: Consider adding error case tests.

While the happy path tests are comprehensive, consider adding test cases for:

  • Invalid URL formats
  • Missing required fields
  • Duplicate link definitions
packages/libraries/federation-link-utils/tests/linkable-spec.spec.ts (1)

5-66: Consider adding edge case tests.

While the current test suite is good, consider adding test cases for:

  • Invalid version formats (e.g., v1, v1.0.0)
  • Version numbers at boundary conditions (e.g., v999.9999)
  • Multiple links with the same identity
packages/libraries/federation-link-utils/tests/index.spec.ts (1)

4-59: Consider refactoring the metadata collection logic for better maintainability.

The metadata collection logic in the v0.1 handler could be simplified by extracting the directive argument parsing into a helper function.

Consider this refactor:

 'v0.1': resolveImportName => (typeDefs: DocumentNode) => {
+  const getDirectiveArgValue = (directive: any, argName: string) => 
+    (directive?.arguments?.find(a => a.name.value === argName)?.value as StringValueNode | undefined)?.value ?? '??';
+
   const collectedMeta: Record<string, Record<string, string>> = {};
   const metaName = resolveImportName('@meta');
   const exampleName = resolveImportName('@example');
   visit(typeDefs, {
     FieldDefinition: node => {
       let metaData: Record<string, string> = {};
       const fieldName = node.name.value;
       const meta = node.directives?.find(d => d.name.value === metaName);
       if (meta) {
-        metaData['name'] =
-          (
-            meta.arguments?.find(a => a.name.value === 'name')?.value as
-              | StringValueNode
-              | undefined
-          )?.value ?? '??';
-        metaData['content'] =
-          (
-            meta.arguments?.find(a => a.name.value === 'content')?.value as
-              | StringValueNode
-              | undefined
-          )?.value ?? '??';
+        metaData['name'] = getDirectiveArgValue(meta, 'name');
+        metaData['content'] = getDirectiveArgValue(meta, 'content');
       }

       const example = node.directives?.find(d => d.name.value === exampleName);
       if (example) {
-        metaData['eg'] =
-          (
-            example.arguments?.find(a => a.name.value === 'eg')?.value as
-              | StringValueNode
-              | undefined
-          )?.value ?? '??';
+        metaData['eg'] = getDirectiveArgValue(example, 'eg');
       }
packages/libraries/federation-link-utils/src/link.ts (3)

5-40: Enhance error handling and type safety.

Consider these improvements:

  1. Add more descriptive warning messages
  2. Consider throwing errors for invalid arguments instead of just logging warnings
  3. Add type guards for better type safety

Consider this refactor:

 function linkFromArgs(args: readonly ConstArgumentNode[]): FederatedLink | undefined {
   let url: FederatedLinkUrl | undefined,
     imports: FederatedLinkImport[] = [],
     as: string | null = null;
   for (const arg of args) {
     switch (arg.name.value) {
       case 'url': {
         if (arg.value.kind === Kind.STRING) {
           url = FederatedLinkUrl.fromUrl(arg.value.value);
         } else {
-          console.warn(`Unexpected kind, ${arg.value.kind}, for argument "url" in @link.`);
+          console.warn(
+            `Invalid @link "url" argument: Expected string, got ${arg.value.kind}. ` +
+            'This may cause federation link resolution to fail.'
+          );
         }
         break;
       }
       case 'import': {
+        if (!FederatedLinkImport.isValidImport(arg.value)) {
+          console.warn(
+            `Invalid @link "import" argument: ${arg.value.kind}. ` +
+            'This may cause federation link resolution to fail.'
+          );
+          break;
+        }
         imports = FederatedLinkImport.fromTypedefs(arg.value);
         break;
       }
       case 'as': {
         if (arg.value.kind === Kind.STRING) {
           as = arg.value.value ?? null;
         } else {
-          console.warn(`Unexpected kind, ${arg.value.kind}, for argument "as" in @link.`);
+          console.warn(
+            `Invalid @link "as" argument: Expected string, got ${arg.value.kind}. ` +
+            'This may cause federation link resolution to fail.'
+          );
         }
         break;
       }

52-74: Consider optimizing array operations and adding input validation.

The fromTypedefs method could be optimized to reduce array operations and add input validation.

Consider this refactor:

 static fromTypedefs(typeDefs: DocumentNode): FederatedLink[] {
+  if (!typeDefs?.definitions?.length) {
+    return [];
+  }
-  let links: FederatedLink[] = [];
   return typeDefs.definitions
-    .filter(definition => 
+    .reduce((links, definition) => {
       if (definition.kind === Kind.SCHEMA_EXTENSION || definition.kind === Kind.SCHEMA_DEFINITION) {
         const defLinks = definition.directives?.filter(
           directive => directive.name.value === 'link',
         );
         const parsedLinks =
           defLinks?.map(l => linkFromArgs(l.arguments ?? [])).filter(l => l !== undefined) ?? [];
-        links = links.concat(parsedLinks);
+        links.push(...parsedLinks);
       }
+      return links;
+    }, [] as FederatedLink[]);
-    return links;
 }

98-114: Improve type handling and documentation for version support methods.

The ts-expect-error comment could be more descriptive, and the methods could benefit from better documentation.

Consider these improvements:

+/**
+ * Checks if this link supports a specific version.
+ * @param version - Version string (e.g., "v2.3"), version numbers, FederatedLinkUrl instance, or null
+ * @returns true if the version is supported
+ */
 supports(version: string): boolean;
 supports(major: number, minor: number): boolean;
 supports(version: FederatedLinkUrl): boolean;
 supports(version: null): boolean;
 supports(...args: [string] | [number, number] | [FederatedLinkUrl] | [null]): boolean {
-  /** @ts-expect-error: ignore tuple error. These are tuples and can be spread. tsc is wrong. */
+  /** @ts-expect-error TS2556: A spread argument must either have a tuple type or be passed to a rest parameter.
+   * This is a false positive as we're explicitly handling tuple types in the function signature.
+   */
   return this.url.supports(...args);
 }
📜 Review details

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

📥 Commits

Reviewing files that changed from the base of the PR and between 379ba8e and 51b4940.

📒 Files selected for processing (7)
  • packages/libraries/federation-link-utils/src/link-url.ts (1 hunks)
  • packages/libraries/federation-link-utils/src/link.ts (1 hunks)
  • packages/libraries/federation-link-utils/src/linkable-spec.ts (1 hunks)
  • packages/libraries/federation-link-utils/tests/index.spec.ts (1 hunks)
  • packages/libraries/federation-link-utils/tests/link-url.spec.ts (1 hunks)
  • packages/libraries/federation-link-utils/tests/link.spec.ts (1 hunks)
  • packages/libraries/federation-link-utils/tests/linkable-spec.spec.ts (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
  • packages/libraries/federation-link-utils/src/link-url.ts
🧰 Additional context used
📓 Path-based instructions (1)
`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/tests/link-url.spec.ts
  • packages/libraries/federation-link-utils/tests/linkable-spec.spec.ts
  • packages/libraries/federation-link-utils/tests/link.spec.ts
  • packages/libraries/federation-link-utils/tests/index.spec.ts
  • packages/libraries/federation-link-utils/src/linkable-spec.ts
  • packages/libraries/federation-link-utils/src/link.ts
📓 Learnings (3)
packages/libraries/federation-link-utils/tests/linkable-spec.spec.ts (1)
Learnt from: jdolle
PR: graphql-hive/console#6452
File: packages/libraries/linkable-specs/src/__tests__/index.spec.ts:25-25
Timestamp: 2025-02-03T03:41:50.270Z
Learning: The @graphql-hive/linkable-specs package handles directive namespacing automatically through its resolveImportName function. Users should use base directive names (e.g., '@example') rather than fully qualified names (e.g., '@metadata_example') as the package manages the namespace transformation internally.
packages/libraries/federation-link-utils/tests/link.spec.ts (1)
Learnt from: jdolle
PR: graphql-hive/console#6452
File: packages/libraries/federation-link-utils/src/__tests__/index.spec.ts:6-7
Timestamp: 2025-02-03T23:57:17.342Z
Learning: The federation-link-utils library supports two styles of directive imports:
1. Named imports (e.g., `@meta`)
2. Namespaced imports (e.g., `@metadata__example`)
Both styles can be used together in the same schema.
packages/libraries/federation-link-utils/tests/index.spec.ts (2)
Learnt from: jdolle
PR: graphql-hive/console#6452
File: packages/libraries/federation-link-utils/src/__tests__/index.spec.ts:6-7
Timestamp: 2025-02-03T23:57:17.342Z
Learning: The federation-link-utils library supports two styles of directive imports:
1. Named imports (e.g., `@meta`)
2. Namespaced imports (e.g., `@metadata__example`)
Both styles can be used together in the same schema.
Learnt from: jdolle
PR: graphql-hive/console#6452
File: packages/libraries/linkable-specs/src/__tests__/index.spec.ts:25-25
Timestamp: 2025-02-03T03:41:50.270Z
Learning: The @graphql-hive/linkable-specs package handles directive namespacing automatically through its resolveImportName function. Users should use base directive names (e.g., '@example') rather than fully qualified names (e.g., '@metadata_example') as the package manages the namespace transformation internally.
⏰ Context from checks skipped due to timeout of 90000ms (9)
  • GitHub Check: test / unit
  • GitHub Check: typescript / typecheck
  • GitHub Check: static-analysis / analyze (typescript)
  • 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
  • GitHub Check: SonarCloud
🔇 Additional comments (8)
packages/libraries/federation-link-utils/src/linkable-spec.ts (2)

28-40: Add validation for version format.

The version sorting logic assumes all version strings are valid. Consider adding validation to handle invalid version formats.

 this.sortedVersionKeys = Object.keys(versions)
   .map(
     (
       key,
     ): {
       key: string;
       split?: number[];
-    } => ({ key, split: key.match(VERSION_KEY_MATCH)?.map(Number).slice(1) }),
+    } => {
+      const match = key.match(VERSION_KEY_MATCH);
+      if (match) {
+        const [major, minor] = match.slice(1).map(Number);
+        if (Number.isNaN(major) || Number.isNaN(minor)) {
+          throw new Error(`Invalid version format: ${key}`);
+        }
+        return { key, split: [major, minor] };
+      }
+      return { key, split: undefined };
+    },
   )

51-52: Address the TODO comment about version ordering.

The comment suggests that the version finding logic should be reversed to find the first version in ascending order. This could affect version compatibility checks.

Could you clarify why the version finding logic needs to be reversed? This would help in implementing the correct solution.

packages/libraries/federation-link-utils/tests/link-url.spec.ts (1)

1-60: Well-structured test suite with good coverage!

The test suite thoroughly covers:

  • URL parsing with various formats
  • Version compatibility checks
  • URL normalization
packages/libraries/federation-link-utils/tests/index.spec.ts (3)

61-82: LGTM! Comprehensive test coverage for Federation 2.x support.

The test effectively validates both named and namespaced directive imports, aligning with the supported directive import styles.


84-99: LGTM! Good error case coverage.

The test effectively validates the fallback behavior when Federation 2 support is missing.


101-119: LGTM! Good version format coverage.

The test effectively validates support for underscores in version identifiers.

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

42-50: LGTM! Clean and focused implementation.

The function correctly handles directive namespacing, including the special case for @ prefixes.


130-140: LGTM! Well-documented and robust implementation.

The method correctly handles all directive import styles and includes clear documentation explaining the @ prefix handling.

return this.as ? `{ name: "${this.name}", as: "${this.as}" }` : `"${this.name}"`;
}

static fromTypedefs(node: ConstValueNode): FederatedLinkImport[] {
Copy link
Member

Choose a reason for hiding this comment

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

If see that this code (and some other in the lib implementation) is throwing errors, maybe we should use better kind of errors instead of just Error with a message? 🤔

Copy link
Member

@dotansimha dotansimha left a comment

Choose a reason for hiding this comment

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

added a few minor comments. let's also wait for @kamilkisiela and @n1ru4l to review?
thanks!

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 (7)
packages/libraries/federation-link-utils/src/index.ts (1)

1-4: Enhance documentation with spec reference.

Consider adding a link to the Federation V2 @link specification for better context and maintainability.

 /**
  * Exposes a simple and efficient API for interacting with Federation V2's `@link` directives
- * according to spec.
+ * according to the Federation V2 specification:
+ * https://www.apollographql.com/docs/federation/federation-spec/
  */
packages/libraries/federation-link-utils/src/link.ts (3)

39-39: Use explicit return undefined.

For better code clarity and maintainability, consider using an explicit return statement.

-  return;
+  return undefined;

9-35: Add validation for duplicate arguments.

The current implementation silently accepts duplicate arguments, which could lead to unexpected behavior. Consider adding validation to ensure each argument appears only once.


53-57: Add input validation in constructor.

Consider adding validation for constructor parameters to ensure the class is initialized with valid data:

   constructor(
     private readonly url: FederatedLinkUrl,
     private readonly as: string | null,
     private readonly imports: FederatedLinkImport[],
-  ) {}
+  ) {
+    if (!url) {
+      throw new Error('URL is required');
+    }
+    if (as !== null && typeof as !== 'string') {
+      throw new Error('If provided, "as" must be a string');
+    }
+    if (!Array.isArray(imports)) {
+      throw new Error('imports must be an array');
+    }
+  }
packages/libraries/federation-link-utils/src/link-url.ts (3)

1-1: Document version number constraints.

The regex allows specific digit limits (3 for major, 4 for minor) and is case-insensitive. Please document these constraints and the rationale behind them.

-const VERSION_MATCH = /v(\d{1,3})\.(\d{1,4})/i;
+/**
+ * Matches version strings in the format v<major>.<minor> where:
+ * - major: 1-3 digits (0-999)
+ * - minor: 1-4 digits (0-9999)
+ * - case-insensitive 'v' prefix
+ */
+const VERSION_MATCH = /v(\d{1,3})\.(\d{1,4})/i;

3-10: Document special return values and add input validation.

The function returns [-1, -1] for invalid versions without documenting this behavior. Also, consider adding input validation.

+/**
+ * Parses a version string in the format v<major>.<minor>.
+ * @param version - The version string to parse
+ * @returns A tuple of [major, minor] version numbers.
+ *          Returns [-1, -1] if the version string is invalid or null.
+ */
 function parseVersion(version: string | null): [number, number] {
+  if (!version) {
+    return [-1, -1];
+  }
   const versionParts = version?.match(VERSION_MATCH);
   if (versionParts?.length) {
     const [_full, major, minor] = versionParts;

35-51: Add input validation and improve URL parsing robustness.

The fromUrl method could benefit from input validation and more robust URL parsing.

   static fromUrl = (urlSource: string): FederatedLinkUrl => {
+    if (!urlSource) {
+      throw new Error('URL source cannot be empty');
+    }
+
+    try {
       const url = new URL(urlSource);
       const parts = url.pathname.split('/').filter(Boolean);
       const versionOrName = parts[parts.length - 1];
       if (versionOrName) {
         if (VERSION_MATCH.test(versionOrName)) {
           const maybeName = parts[parts.length - 2];
           return new FederatedLinkUrl(
             url.origin + (maybeName ? `/${parts.slice(0, parts.length - 1).join('/')}` : ''),
             maybeName ?? null,
             versionOrName,
           );
         }
         return new FederatedLinkUrl(`${url.origin}/${parts.join('/')}`, versionOrName, null);
       }
       return new FederatedLinkUrl(url.origin, null, null);
+    } catch (error) {
+      throw new Error(`Invalid URL format: ${error.message}`);
+    }
   };
📜 Review details

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

📥 Commits

Reviewing files that changed from the base of the PR and between 51b4940 and 5cc162e.

📒 Files selected for processing (6)
  • packages/libraries/federation-link-utils/README.md (1 hunks)
  • packages/libraries/federation-link-utils/package.json (1 hunks)
  • packages/libraries/federation-link-utils/src/index.ts (1 hunks)
  • packages/libraries/federation-link-utils/src/link-url.ts (1 hunks)
  • packages/libraries/federation-link-utils/src/link.ts (1 hunks)
  • packages/libraries/federation-link-utils/tests/index.spec.ts (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (3)
  • packages/libraries/federation-link-utils/README.md
  • packages/libraries/federation-link-utils/package.json
  • packages/libraries/federation-link-utils/tests/index.spec.ts
🧰 Additional context used
📓 Path-based instructions (1)
`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-url.ts
  • packages/libraries/federation-link-utils/src/link.ts
⏰ Context from checks skipped due to timeout of 90000ms (10)
  • GitHub Check: typescript / typecheck
  • GitHub Check: static-analysis / analyze (typescript)
  • GitHub Check: alpha / cli-artifacts
  • GitHub Check: static-analysis / analyze (javascript)
  • GitHub Check: code-style / eslint-and-prettier
  • GitHub Check: test / unit
  • GitHub Check: build / dockerize (linux/arm64)
  • GitHub Check: build / dockerize (linux/amd64)
  • GitHub Check: test / db-migrations
  • GitHub Check: SonarCloud
🔇 Additional comments (7)
packages/libraries/federation-link-utils/src/index.ts (3)

9-13: LGTM! Well-defined types and constants.

The LinkVersion type effectively captures all possible version formats, including the special case for Federation V1 as mentioned in the PR objectives.


15-38: Consider renaming the function to extractLinkedImplementations.

As suggested in a previous review, the name extractLinkedImplementations might better reflect the function's purpose.

-export function extractLinkImplementations(typeDefs: DocumentNode): {
+export function extractLinkedImplementations(typeDefs: DocumentNode): {

39-76: LGTM! Robust implementation with good error handling.

The implementation effectively:

  • Handles both Federation V1 and V2 as discussed in the PR objectives
  • Provides clear error messages guiding users to check implementation status
  • Comprehensively handles all version formats
packages/libraries/federation-link-utils/src/link.ts (2)

42-50: LGTM! Clean and efficient implementation.

The function correctly handles both regular and directive names, with proper edge case handling for '@' prefixed names.


60-73: LGTM! Well-implemented static factory method.

The fromTypedefs method efficiently collects and processes @link directives from GraphQL type definitions.

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

81-89: LGTM! Well-implemented version compatibility logic.

The version compatibility implementation correctly handles:

  • Federation 2 versioning requirements
  • Special case for v0.x versions requiring exact matches
  • Standard version comparison for v1+ versions

12-15: Verify alignment with Federation 2 requirements.

The implementation aligns with the PR objectives for managing Federation 2 link directives. However, let's verify that the version constraints match the Federation 2 specification.

✅ Verification successful

🌐 Web query:

What are the version number constraints in Federation 2 link specification?

💡 Result:

Apollo Federation 2 introduces specific versioning requirements for its @link directive and tooling compatibility:

Version Constraints in @link Directives

  1. Explicit Version Specification
    Subgraphs must declare Federation 2 compatibility using an exact version in their @link URL:

    extend schema
      @link(url: "https://specs.apollo.dev/federation/v2.x", import: [...])

    Supported versions range from v2.0 to v2.9[7][10]. Partial matches like v2 are invalid – full semantic versioning (e.g., v2.3) is required[1][7].

  2. Backward Compatibility
    Newer minor versions (e.g., v2.5) include all features from prior versions (v2.0-v2.4)[7]. Subgraphs using older versions remain compatible with newer composition tools[1][9].

Tooling Requirements

Component Version Constraints
@apollo/gateway ≥ 2.x supports Fed 1 & 2 schemas[1]
GraphOS Router Requires explicit Fed 2 opt-in[1][9]
Rover CLI ≥ 0.5.x for Fed 2 composition[1][9]
Subgraph Libraries Must support Fed 2 directives (e.g., @apollo/subgraph ≥ 2.x)[1][3]

Key Constraints

  • Managed Federation requires explicit Fed 2 opt-in via Apollo Studio[1][9]
  • Mixed Environments allow incremental adoption – Fed 1 and Fed 2 subgraphs can coexist during migration[1]
  • Directive Import must explicitly list all used federation directives in @link(import:)[1][7]

For example, valid Fed 2 declarations include:

extend schema @link(url: "https://specs.apollo.dev/federation/v2.3", 
                   import: ["@key", "@shareable"])

While invalid declarations would omit the full minor version or mismatch tooling capabilities[7][10]. Always verify your gateway/router supports your chosen Fed 2.x version before deployment[1][9].

Citations:


Federation 2 Version Constraints Comply with Specifications

  • The code and its documentation enforce the use of explicit semantic versions (e.g., “v2.3”) for Federation 2, as required.
  • Partial versions like “v2” are disallowed, matching the Apollo Federation 2 specification.
  • The version range (e.g., from v2.0 to v2.9) and explicit minor version requirement are consistent with the documentation.

if (arg.value.kind === Kind.STRING) {
url = FederatedLinkUrl.fromUrl(arg.value.value);
} else {
console.warn(`Unexpected kind, ${arg.value.kind}, for argument "url" in @link.`);
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

Replace console.warn with a proper logging mechanism.

As this is a library package that will be used by customers, using console.warn directly is not recommended. Consider:

  1. Using a proper logging interface that can be configured by the consuming application
  2. Throwing validation errors instead of warnings for invalid argument types

Also applies to: 27-27

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 (2)
packages/libraries/federation-link-utils/README.md (2)

13-17: Enhance features section with more details and consistent formatting.

While the features are clearly listed, consider these improvements:

  1. Use consistent code formatting for technical terms (e.g., `graphql` as a peer dependency).
  2. Add brief explanations for each feature to help users understand their benefits.
 ## Features
 
 - Link version support.
 - Import `as`/namespacing support that follows the [link spec](https://specs.apollo.dev/link/v1.0/).
-- Only `graphql` as a peer dependency.
+- Only `` `graphql` `` as a peer dependency.
+
+### Feature Details
+
+- **Link version support**: Enables handling multiple versions of Federation 2 features.
+- **Import `as`/namespacing support**: Follows the link specification for proper import naming and namespacing.
+- **Minimal dependencies**: Only requires `` `graphql` `` as a peer dependency, keeping the library lightweight.

19-65: Enhance usage documentation with additional examples and error handling.

The current example is good but could be more comprehensive. Consider adding:

  1. More inline comments explaining the TypeScript code's functionality
  2. Error handling examples
  3. Additional use cases (e.g., multiple versions, different directives)
 // specs.ts
 import { extractLinkImplementations } from '@graphql-hive/federation-link-utils'
 
 const typeDefs = parse(sdl)
+// Extract link implementations and get utility functions
 const { matchesImplementation, resolveImportName } = extractLinkImplementations(typeDefs);
+
+// Check if the schema implements the specific version of our spec
 if (matchesImplementation('https://specs.graphql-hive.com/example', 'v1.0')) {
   const examples: Record<string, string> = {}
+  // Resolve the actual directive name used in the schema (handles 'as' imports)
   const exampleName = resolveImportName('https://specs.graphql-hive.com/example', '@example')
+
+  try {
     visit(typeDefs, {
       FieldDefinition: node => {
         const example = node.directives?.find(d => d.name.value === exampleName)
         if (example) {
           examples[node.name.value] = (
             example.arguments?.find(a => a.name.value === 'eg')?.value as
               | StringValueNode
               | undefined
           )?.value
         }
       }
     })
     return examples
+  } catch (error) {
+    console.error('Failed to process schema:', error)
+    throw error
+  }
 }
+
+// Example with multiple versions
+if (matchesImplementation('https://specs.graphql-hive.com/example', 'v2.0')) {
+  // Handle v2.0 implementation
+}
📜 Review details

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

📥 Commits

Reviewing files that changed from the base of the PR and between 5cc162e and 998f5a4.

📒 Files selected for processing (1)
  • packages/libraries/federation-link-utils/README.md (1 hunks)
🧰 Additional context used
📓 Path-based instructions (1)
`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/README.md
⏰ Context from checks skipped due to timeout of 90000ms (9)
  • GitHub Check: test / unit
  • GitHub Check: typescript / typecheck
  • GitHub Check: static-analysis / analyze (typescript)
  • 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
  • GitHub Check: SonarCloud
🔇 Additional comments (2)
packages/libraries/federation-link-utils/README.md (2)

1-5: LGTM! Clear and informative introduction.

The introduction effectively establishes context and provides relevant links to help users understand the project's background.


9-11: LGTM! Clear purpose statement with relevant documentation link.

The library's purpose is well-defined and includes a helpful link to the Federation @link directive documentation.

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: 2

🧹 Nitpick comments (15)
packages/services/schema/src/lib/compose.ts (5)

95-142: Performance consideration for supergraph-to-public schema
This method transforms the supergraph to a public schema on every composition. Consider caching for large-scale usage to avoid repeated parse/print overhead.


259-283: Broker-based external calls
This thin wrapper around callExternalService is clear. You might consider merging if usage remains minimal.


285-414: Robust external compose call
Comprehensive retry logic, structured error objects, and tracing with spans. Watch out for potential overhead of 5 retries. Adjust if needed for performance-critical environments.


416-419: Explanatory error mapping
codeToExplanationMap is well-structured for known error codes. For additional clarity, consider logging unknown codes to assist troubleshooting.


421-427: translateMessage fallback
The fallback to no message is acceptable, but providing a default fallback string for unknown codes might improve debugging.

packages/services/schema/src/lib/federation-tag-extraction.ts (2)

401-404: Extracting tags from document
The function effectively collects directive-based tags. Consider validating unexpected arguments or missing directive fields if the schema might contain partial data.


438-440: Minor doc comment
You might expand these lines to better explain the purpose of the extraction or link relevant resources.

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

216-221: Extracting global tags
Automatically extracting tags from the supergraph is helpful for additional logic. Consider verifying the performance impact for extremely large schemas.


279-279: Preserving subgraph identity
Spreading subgraph fields while replacing typeDefs ensures minimal duplication.


282-282: Asynchronous composition
Awaits each contract’s composition. Works seamlessly unless concurrency needs bounding with a queue or pool.

packages/services/schema/src/lib/errors.ts (1)

10-29: Consider using Zod for input validation.

Since the codebase prefers Zod for input/output validations, consider using it to validate the error structure.

+import { z } from 'zod';
+
+const CompositionFailureErrorSchema = z.object({
+  message: z.string(),
+  source: z.enum(['graphql', 'composition']),
+});
+
 export function toValidationError(error: any, source: CompositionErrorSource) {
+  const errorMessage = error instanceof GraphQLError || error instanceof Error
+    ? error.message
+    : String(error);
+
+  return CompositionFailureErrorSchema.parse({
+    message: errorMessage,
+    source,
+  });
-  if (error instanceof GraphQLError) {
-    return {
-      message: error.message,
-      source,
-    };
-  }
-
-  if (error instanceof Error) {
-    return {
-      message: error.message,
-      source,
-    };
-  }
-
-  return {
-    message: error as string,
-    source,
-  };
 }
packages/services/schema/src/lib/add-inaccessible-to-unreachable-types.ts (2)

6-19: Enhance JSDoc documentation.

The JSDoc comments should include parameter descriptions and return type information.

 /**
  * Adds inaccessible directive to unreachable types
  *
- * @param resolveName
- * @param compositionResult
- * @param supergraphSDL
- * @returns
+ * @param resolveName - Function to resolve directive names
+ * @param compositionResult - Result of the composition operation
+ * @param supergraphSDL - The supergraph schema in DocumentNode format
+ * @returns {ComposerMethodResult} Updated composition result with inaccessible directives added
  */

20-32: Consider extracting federation types to a constant.

The federation types set could be moved to a constant to improve maintainability and reusability.

+const FEDERATION_TYPES = [
+  ['https://specs.apollo.dev/join', 'FieldSet'],
+  ['https://specs.apollo.dev/join', 'Graph'],
+  ['https://specs.apollo.dev/link', 'Import'],
+  ['https://specs.apollo.dev/link', 'Purpose'],
+  ['https://specs.apollo.dev/federation', 'Policy'],
+  ['https://specs.apollo.dev/federation', 'Scope'],
+  ['https://specs.apollo.dev/join', 'DirectiveArguments'],
+] as const;
+
 export const addInaccessibleToUnreachableTypes = (
   resolveName: (identity: string, name: string) => string,
   compositionResult: ComposerMethodResult,
   supergraphSDL: DocumentNode,
 ): ComposerMethodResult => {
   const inaccessibleDirectiveName = resolveName(
     'https://specs.apollo.dev/inaccessible',
     '@inaccessible',
   );
-  const federationTypes = new Set([
-    resolveName('https://specs.apollo.dev/join', 'FieldSet'),
-    resolveName('https://specs.apollo.dev/join', 'Graph'),
-    resolveName('https://specs.apollo.dev/link', 'Import'),
-    resolveName('https://specs.apollo.dev/link', 'Purpose'),
-    resolveName('https://specs.apollo.dev/federation', 'Policy'),
-    resolveName('https://specs.apollo.dev/federation', 'Scope'),
-    resolveName('https://specs.apollo.dev/join', 'DirectiveArguments'),
-  ]);
+  const federationTypes = new Set(
+    FEDERATION_TYPES.map(([identity, name]) => resolveName(identity, name))
+  );
packages/services/schema/src/lib/__tests__/add-inaccessible-to-unreachable-types.spec.ts (1)

6-28: Add more test cases for edge scenarios.

The test only covers a basic scenario. Consider adding tests for:

  • Empty schema
  • Schema with only federation types
  • Schema with circular dependencies

Example test case:

test('Handles empty schema', async () => {
  let compositionResult = composeFederationV2(
    [{
      name: 'empty',
      url: 'https://empty-example.graphql-hive.com',
      typeDefs: parse('type Query { _empty: String }'),
    }],
    console as any,
  );
  // ... rest of the test
});
packages/libraries/federation-link-utils/src/index.ts (1)

6-8: Consider using type-only imports for DocumentNode.

Since DocumentNode is only used as a type, consider using a type-only import to optimize the bundle size.

-import type { DocumentNode } from 'graphql';
+import { type DocumentNode } from 'graphql';
📜 Review details

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

📥 Commits

Reviewing files that changed from the base of the PR and between 998f5a4 and e345a13.

⛔ Files ignored due to path filters (1)
  • pnpm-lock.yaml is excluded by !**/pnpm-lock.yaml
📒 Files selected for processing (12)
  • integration-tests/tests/schema/contracts.spec.ts (0 hunks)
  • packages/libraries/federation-link-utils/src/index.ts (1 hunks)
  • packages/services/schema/package.json (1 hunks)
  • packages/services/schema/src/api.ts (1 hunks)
  • packages/services/schema/src/lib/__tests__/add-inaccessible-to-unreachable-types.spec.ts (1 hunks)
  • packages/services/schema/src/lib/add-inaccessible-to-unreachable-types.ts (1 hunks)
  • packages/services/schema/src/lib/compose.ts (1 hunks)
  • packages/services/schema/src/lib/errors.ts (1 hunks)
  • packages/services/schema/src/lib/federation-tag-extraction.spec.ts (1 hunks)
  • packages/services/schema/src/lib/federation-tag-extraction.ts (5 hunks)
  • packages/services/schema/src/orchestrators.ts (5 hunks)
  • packages/services/schema/src/types.ts (1 hunks)
💤 Files with no reviewable changes (1)
  • integration-tests/tests/schema/contracts.spec.ts
✅ Files skipped from review due to trivial changes (1)
  • packages/services/schema/src/types.ts
🧰 Additional context used
📓 Path-based instructions (2)
`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/package.json
  • packages/services/schema/src/api.ts
  • packages/services/schema/src/lib/add-inaccessible-to-unreachable-types.ts
  • packages/services/schema/src/lib/errors.ts
  • packages/services/schema/src/lib/__tests__/add-inaccessible-to-unreachable-types.spec.ts
  • packages/services/schema/src/orchestrators.ts
  • packages/services/schema/src/lib/federation-tag-extraction.ts
  • packages/services/schema/src/lib/federation-tag-extraction.spec.ts
  • packages/services/schema/src/lib/compose.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
⏰ 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: 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
  • GitHub Check: SonarCloud
🔇 Additional comments (31)
packages/services/schema/src/lib/compose.ts (7)

1-18: Imports and basic setup look good
No major issues found. The usage of built-in Node crypto, got, zod, and Sentry is well-structured.


19-58: Validate potential large payload concerns
The EXTERNAL_COMPOSITION_RESULT schema provides a robust shape for external composition results. However, if the external service returns very large bodies, consider adding more safeguards, such as request size or schema-level constraints.


59-93: Apollo Federation v1 composition logic
Implementation is consistent with Federation v1. Good error-checking for composition errors.


144-223: External composition function
Logic for building the body, generating an HMAC signature, and validating the response via zod is well-organized. Error handling is comprehensive. No immediate security concerns.


224-245: SDL validity check
checkExternalCompositionCompatibility properly warns when validation errors arise. Confirm that silent failures aren’t introduced if minimal logging is enforced in production.


247-253: Graceful error transformation
errorWithPossibleCode effectively converts GraphQLErrors into structured validation errors. This helps unify error handling across the codebase.


255-257: Secret hashing uses secure defaults
Creating an HMAC with UTF-8 input is a standard approach. Ensure the secret is strong enough and updated regularly.

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

20-20: Leverages the new federation-link-utils
Importing extractLinkImplementations aligns with the new library approach, removing duplication from older custom implementations.


108-113: Resolving directive names
Good usage of resolveImportName for @inaccessible and @tag. This strategy keeps the code flexible and version-agnostic.


356-361: Applying the tag filter to subgraphs
Mapping subgraphs and merging the filter transformation is concise. Confirm that logging or debugging is available if a subgraph transform fails unexpectedly.


422-435: Dynamic tag directive name extraction
createTagDirectiveNameExtractionStrategy is well-structured. If future directives gain extra arguments beyond name, ensure graceful handling.

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

16-35: Updated imports & dependencies
Imports for extractLinkImplementations and composeFederationV1/V2 centralize the composition logic. This is a beneficial modular approach.


178-178: Composition method determination
Inline comment helps clarify the selection logic for Federation v1, Federation v2, or external. No changes needed.


211-211: Typed composition result
Capturing CompositionResult with the extension fields is a good practice for clarity and type safety.


223-228: Combining composition outcome
Merging in the composed result with the spread operator is straightforward. Confirm that no fields inadvertently overwrite prior data.


234-235: Early exit on no contracts
Short-circuiting is a clear approach. Good usage of optional chaining.


239-239: Skipping contracts on failure
Skipping contract composition if the main composition fails is logical. Preserves the simpler error shape.


262-262: Comment clarity
The inline comment clarifies the upcoming contract composition process. No changes needed.


265-265: Replaces tags with inaccessible directives
This ensures only relevant tags remain accessible, aligning with the contract’s scope.


267-274: Fan-out approach to multiple tag filters
Constructing a filter object for each contract is a simple yet powerful pattern. Watch for potential memory overhead with many contracts.


290-297: Adding inaccessible directives to unreachable types
Conditional logic is well-structured. If the contract demands it, we further parse the supergraph and call addInaccessibleToUnreachableTypes.

packages/services/schema/src/lib/errors.ts (2)

3-8: LGTM! Well-defined error types.

The type definitions are clear and follow TypeScript best practices.


31-33: LGTM! Clean higher-order function implementation.

The errorWithSource function is well-implemented as a higher-order function, making it easy to create specialized error handlers.

packages/services/schema/src/lib/add-inaccessible-to-unreachable-types.ts (1)

34-37: LGTM! Good early return pattern.

The early return with a clear comment explaining the reason is a good practice.

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

10-10: LGTM! Good modularization of error types.

Moving error types to a dedicated module improves code organization.

packages/services/schema/src/lib/__tests__/add-inaccessible-to-unreachable-types.spec.ts (1)

30-100: LGTM! Good use of snapshots for schema validation.

The test effectively validates both the supergraph and public schema transformations.

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

1-5: LGTM! Clear and concise documentation.

The file header effectively communicates the purpose of the utility and its relationship to the Federation V2 specification.


10-12: LGTM! Well-defined type and constant.

The FEDERATION_V1 symbol and LinkVersion type are well-defined and provide good type safety for version handling.


14-82: LGTM! Well-structured and documented function.

The extractLinkImplementations function is:

  • Well-documented with clear JSDoc comments
  • Handles edge cases properly
  • Returns a clean interface with two methods

However, based on the past review comment, consider renaming the function:

-export function extractLinkImplementations(typeDefs: DocumentNode): {
+export function extractLinkedImplementations(typeDefs: DocumentNode): {
packages/services/schema/src/lib/federation-tag-extraction.spec.ts (1)

1662-1664: Consider adding the federation link directive.

The comment indicates that federation must be linked, but the directive is commented out. Consider either:

  1. Adding the federation link directive if it's required
  2. Clarifying in the comment why the directive is commented out
packages/services/schema/package.json (1)

14-14: LGTM! Correctly added workspace dependency.

The new dependency @graphql-hive/federation-link-utils is properly added as a workspace dependency, consistent with the project's dependency management pattern.

Comment on lines +39 to +60
// we retrieve the list of reachable types from the public api sdl
const reachableTypeNames = getReachableTypes(parse(compositionResult.result.sdl!));
// apollo router does not like @inaccessible on federation types...
for (const federationType of federationTypes) {
reachableTypeNames.add(federationType);
}

// then we apply the filter to the supergraph SDL (which is the source for the public api sdl)
supergraphSDL = addDirectiveOnTypes({
documentNode: supergraphSDL,
excludedTypeNames: reachableTypeNames,
directiveName: inaccessibleDirectiveName,
});
return {
...compositionResult,
result: {
...compositionResult.result,
supergraph: print(supergraphSDL),
sdl: print(transformSupergraphToPublicSchema(supergraphSDL)),
},
};
};
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 adding error handling for transformation failures.

The transformation operations could fail and should be handled gracefully.

   // then we apply the filter to the supergraph SDL (which is the source for the public api sdl)
-  supergraphSDL = addDirectiveOnTypes({
-    documentNode: supergraphSDL,
-    excludedTypeNames: reachableTypeNames,
-    directiveName: inaccessibleDirectiveName,
-  });
+  try {
+    supergraphSDL = addDirectiveOnTypes({
+      documentNode: supergraphSDL,
+      excludedTypeNames: reachableTypeNames,
+      directiveName: inaccessibleDirectiveName,
+    });
+    return {
+      ...compositionResult,
+      result: {
+        ...compositionResult.result,
+        supergraph: print(supergraphSDL),
+        sdl: print(transformSupergraphToPublicSchema(supergraphSDL)),
+      },
+    };
+  } catch (error) {
+    return {
+      type: 'failure',
+      errors: [{
+        message: `Failed to add inaccessible directive: ${error.message}`,
+        source: 'composition'
+      }]
+    };
+  }
-  return {
-    ...compositionResult,
-    result: {
-      ...compositionResult.result,
-      supergraph: print(supergraphSDL),
-      sdl: print(transformSupergraphToPublicSchema(supergraphSDL)),
-    },
-  };
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
// we retrieve the list of reachable types from the public api sdl
const reachableTypeNames = getReachableTypes(parse(compositionResult.result.sdl!));
// apollo router does not like @inaccessible on federation types...
for (const federationType of federationTypes) {
reachableTypeNames.add(federationType);
}
// then we apply the filter to the supergraph SDL (which is the source for the public api sdl)
supergraphSDL = addDirectiveOnTypes({
documentNode: supergraphSDL,
excludedTypeNames: reachableTypeNames,
directiveName: inaccessibleDirectiveName,
});
return {
...compositionResult,
result: {
...compositionResult.result,
supergraph: print(supergraphSDL),
sdl: print(transformSupergraphToPublicSchema(supergraphSDL)),
},
};
};
// we retrieve the list of reachable types from the public api sdl
const reachableTypeNames = getReachableTypes(parse(compositionResult.result.sdl!));
// apollo router does not like @inaccessible on federation types...
for (const federationType of federationTypes) {
reachableTypeNames.add(federationType);
}
// then we apply the filter to the supergraph SDL (which is the source for the public api sdl)
try {
supergraphSDL = addDirectiveOnTypes({
documentNode: supergraphSDL,
excludedTypeNames: reachableTypeNames,
directiveName: inaccessibleDirectiveName,
});
return {
...compositionResult,
result: {
...compositionResult.result,
supergraph: print(supergraphSDL),
sdl: print(transformSupergraphToPublicSchema(supergraphSDL)),
},
};
} catch (error) {
return {
type: 'failure',
errors: [{
message: `Failed to add inaccessible directive: ${error.message}`,
source: 'composition'
}]
};
}
};

`),
},
],
console as any,
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

Avoid using console as logger in tests.

Using console as any is not a good practice. Consider creating a proper mock logger.

const mockLogger = {
  info: jest.fn(),
  warn: jest.fn(),
  error: jest.fn(),
};

@jdolle jdolle merged commit 1d43b24 into main Feb 12, 2025
29 checks passed
@jdolle jdolle deleted the linkable-spec-lib branch February 12, 2025 15:42
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