Metadata Structs
At a glanceβ
- Identifier: MetadataStructs
- Stage: RFC0: Strawman
- Champion: -
- PR: -
Timelineβ
- RFC document updated on 2022-08-16 by Benjie
- RFC document created on 2022-07-07 by Benjie Gillam
Metadata Structs
THIS PROPOSAL HAS BEEN SUPERCEDED BY AnnotationStructs.
Schema metadata has long been desired, please see this long discussion:
https://github.com/graphql/graphql-spec/issues/300
I digested many of the currently proposed (and in the wild) solutions to this problem in my talk at the GraphQL Conference, you can see the talk here:
It covers:
- custom introspection extensions
- 'SDL' field in schema, like Apollo Federation
- storing metadata in the description field
- adding metadata entirely in user-space
- 'applied directives'
It expands on the pros and cons of these approaches and asks "is there a better solution".
Problemsβ
Some of the main problems that need to be solved with schema metadata are:
- representing all desired metadata (including polymorphic metadata)
- the need for granularity (partial introspection)
- the need for support in tooling (e.g. GraphiQL) to give visibility into the metadata
- being able to fully introspect the GraphQL schema in a small number of roundtrips
- avoiding the need for complex parsing on the client
- allowing for future expansion of the metadata/introspection schema (without namespace clashes)
Granularityβ
It can be useful for clients to include small introspection queries as part of their applications - for example you might introspect a particular named enum to make available sorting options in a dropdown. If the schema adds support for a new sort method, the client could add this option to the dropdown without needing to be updated thanks to introspection. However, enum values don't currently contain enough information for this.
Consider that we add a "label" property to the metadata for each enum value - then we would have all we need to display it to the user, so long as they spoke that language. To cater to an international audience, we could add many translations to each enum value - but now the size of the introspection has grown. A better solution might be to allow the client to select just the translation that it needs from the enum value. (We also don't need any of the other metadata for the enum values, only the labels.)
Solutionβ
This RFC proposes what I feel is a more capable and elegant solution than any of the previously proposed solutions covered by my talk, but it's predicated on the existence of a polymorphic-capable composite type that can be used symmetrically for both input and output. As it happens there's an RFC for that, so you can see this metadata RFC as an extension of that Struct RFC.
SDLβ
meta
keyword, very similar todirective
, defines the meta types and fields. The type of each field can be any type that is suitable on both input and output (i.e. scalar, enum, struct, struct union, and wrapping types thereof).meta +source(table: String, column: String, service: ServiceSource) on OBJECT | FIELD_DEFINITION
meta +visibility(only: [VisibilityScope!]!) required on OBJECT
meta +label(en: String, fr: String, de: String) on ENUM_VALUE
struct ServiceSource {
serviceName: string
identifier: string
}
enum VisibilityScope {
NONE
PERSONAL
TEAM
ORGANIZATION
ADMINS
PUBLIC +label(en: "Anyone", fr: "Tout les monde", de: "Alle")
}
type User +source(table: "public.users") +visibility(only: [ORGANIZATION]) {
id: ID!
organization: Organization!
username: String! +source(column: "handle")
avatar: String! +source(service: {
serviceName: "S3"
identifier: "/avatars/27.png"
})
}Note:
+visibility
is marked asrequired
; in user space it must be defined explicitly for each object, but for introspection types it is omitted.Introspectionβ
Introspection query example:
{
User: __type(name: "User") {
meta
# Or:
meta {
source {
table
}
visibility {
only
}
}
}
VisibilityScope: __type(name: "VisibilityScope") {
enumValues {
name
meta {
label {
en
}
}
}
}
}Changes to the schema introspection types:
type __Schema {
metas: [__Meta!]!
}
# Similar to __Directive
type __Meta {
name: String!
description: String
locations: [__MetaLocation!]!
isRequired: Boolean!
#Β Always a struct; the fields of the struct are the parameters of the meta
type: __Type
}
type __Type {
#...
meta: __TypeMeta
}
struct __TypeMeta {
source: __Meta_source
# Though +visibility was defined as 'required', it is only required on
# user-space object types, and thus it is nullable here.
visibility: __Meta_visibility
}
# Auto-generated via `__Meta_` + meta name?
struct __Meta_source {
table: String
service: ServiceSource
}
struct __Meta_visibility {
only: [VisibilityScope!]!
}
type __Field {
meta: __FieldMeta
}
type __EnumValue {
meta: __EnumValueMeta
}
# etc