Composition for Grafbase extensions
In the Grafbase CLI and the schema registry in the Grafbase Platform, composition is handled by our open source graphql-composition crate. It has baked in support for Grafbase extensions.
When composition detects that an imported type or directive comes from an extension, it will add extra information about it in the composed execution schema (the one used by the Gateway, not the public API), so that the Gateway has all the information it needs on which directive, in which subgraph, is associated with which extension.
This information is necessary for resolvers, but optional for authorization and contracts extensions. The later can only rely on the @link url as presented in the second section.
For a directive to be composed as an extension directive, it must be imported from an @linked schema, and that schema's URL must either:
- Use the
file:scheme. - Have a
urlthat starts withhttps://grafbase.com/extensions
In the following example, all @link directives would be interpreted as linking to extensions, and all directives from these extensions would be composed as extension directives:
extend schema
@link(url: "file:///path/to/extension", import: ["@test"])
@link(url: "https://grafbase.com/extensions/kafka/0.2.1")
@link(url: "file:///path/to/another/extension/build", as: "alias")
@link(
url: "https://grafbase.com/extensions/rest/0.5.0"
import: ["@restEndpoint", "@rest"]
)Concretely, composition will produce an extension__Link enum referencing all the extensions, and an @extension__directive directive wrapping each application of each directive from an extension with information about what subgraph it originates from. It can look like this:
type Doctor
@join__type(graph: APPOINTMENTS, key: "id")
@join__type(graph: DOCTORS, key: "id")
{
appointments: [Appointment!]! @join__field(graph: APPOINTMENTS)
firstName: String! @join__field(graph: DOCTORS)
id: ID!
lastName: String! @join__field(graph: DOCTORS)
phone: String @extension__directive(graph: DOCTORS, extension: REST, name: "call", arguments: {method: GET, url: "https://my-phone_registry/doctors/{id}"}) @join__field(graph: DOCTORS)
specialty: String! @join__field(graph: DOCTORS)
}
enum extension__Link
{
KAFKA @extension__link(url: "https://grafbase.com/extensions/kafka/v1.0.0")
ORACLE_CONNECTOR @extension__link(url: "file:///home/lellison/src/oracle-grafbase-extension/dist")
REST @extension__link(url: "https://grafbase.com/extensions/rest")
}The gateway primarily relies on the name and version detected through the @link specification with a few differences:
- the
vprefix for the version number is optional - file path may have a
builddirectory suffix. - in case of file paths, the gateway will try to load a
manifest.json, the one generated bygrafbase extension build. If it finds one it will the name and version specified by it and otherwise fallback on the@linkname & version.
So for https://grafbase.com/extensions/kafka/0.2.1 it's interpreted as the kafka extension version 0.2.1. And for both file:///path/to/extension and file:///path/to/another/extension/build, they're mapped to the extension named extension if no manifest.json was found.
The former section is most relevant if you download extensions from our marketplace or work locally on an extension. But extensions allow for more flexibility by allowing any @link URL to be associated with them. By specifying the following:
# or `authorization.directives` for authorization extensions.
[contracts.directives]
link_urls = ["https://example.com/tag"]And using @composeDirective in your subgraph schema to propagate the directives as such:
extend schema
@link(url: "https://specs.apollo.dev/federation/v2.3", import: ["@composeDirective"])
@link(url: "https://example.com/tag", import: ["@tag"])
@composeDirective(name: "@tag")This allows you to associate your custom namespace to an extension directive. The @tag directive here will be treated as an extension directive by the gateway. You can associate multiple @link URLs to a single extensions, but they must be exact matches. The gateway will still validate the directive names and arguments according to the definitions.graphql file, they must be present and defined there.