Skip to main content

"Metadata Directives" Proposal

At a glance

Timeline


Given the revived discussion around schema metadata and their relationship with SDL directives, I'd like to propose a high level direction for this. My goal here is to introduce something with as few additional moving parts as possible. It's intended to be simple rather than robust.

The primary new mechanism is being able to mark an SDL Directive definition as metadata and exposing such directive usages via introspection. Given an introspection result you should be able to re-create the SDL with directive usages in the right places.

The main downside is that the introspection encoding is simple to the point of requiring post-processing. There is no explicit sub-selection; all metadata gets exposed in introspection. The metadata is not in a structured JSON form, but in arrays of named groups - however this more clearly maps to the way directives are being used. It's up to tools consuming metadata to interpret these as intended.

I'd love feedback on this!

# An SDL directive can now be marked as `metadata`, mirroring how it can be 
# marked as `repeatable`. Doing so causes usages of it within a schema to be
# visible via introspection.

directive @myCustomMetadata(myValue: String) metadata repeatable on FIELD

# This appears in introspection as well. `__Directive` gains an `isMetadata`
# field, again mirroring the existing `isRepeatable` field.

type __Directive {
# ...
isMetadata: Boolean
}

# Once a directive is marked as a "metadata" directive, then usages of it appear
# as metadata at that location in the schema.
#
# Schema introspection types all get a `metadata` field, of type `[__Metadata]`.
# This allows multiple pieces of metadata per location in an introspected schema.
#
# As `metadata` is a list, this allows for retaining their order (which spec
# preserves as potentially meaningful) as well as repeated directives.
#
# No changes are needed to SDL, since we're using existing directive syntax.

type __Field {
# ...
metadata: [__Metadata!] # Example of this being added to all things.
}

# The `__Metadata` type models a single metadata directive usage at a location.
# Therefore it both refers to the directive definition being used, as well as
# the values being used.
#
# Note that `values` could be null in the case of a directive usage without args
# ex: `{ myField @myDirective }`. Also note that since `values` is a list, that
# the order of the directive arguments can be preserved.

type __Metadata {
"""
Additional information provided for an item in a GraphQL schema.
"""

directive: __Directive!
values: [__MetadataValue!]
}

# A `__MetadataValue` is very similar to the existing `__InputValue`. However,
# as it represents a provided value instead of a definition of a value, only the
# `name` and `value` fields are relevant.
#
# Similar to `defaultValue` in `__InputValue`, the `value` argument is a String
# encoding using the GraphQL language. This allows all varities of values which
# can be modeled as a GraphQL value. Tools may prefer to convert this to JSON
# after fetching.

type __MetadataValue {
name: String!
value: String!
}

That's it! Let's take a look at an example:

# For this type's `myField` field, a metadata directive is used with a value provided.

type MyType {
myField: Int @myCustomMetadata(myValue: "some value")
}

# Given a typical introspection query, here's some additional fields queried on
# field during introspection:

fragment FieldMetadata on __Field {
metadata {
directive { name }
values {
name
value
}
}
}

And here's the resulting introspection response (in JSON5) for this particular fragment applying to the myField

{
name: "myField",
// ....
metadata: [
{
directive: { name: "myCustomMetadata" },
values: [
{
name: "myValue",
value: "\"some value\"" // Note GraphQL encoding.
}
]
}
]
}