GraphQL directives
GraphQL only A plain HTTP API has no schema; it uses Cache-Control and the Surrogate-Key header instead.
GraphPilot reads these directives from the schema you deploy, when it builds the deployment. None of them is forwarded to or needed by your origin. What each one is for is on the pages linked from each section; this page is the exact surface.
| Directive | Decides | Concept |
|---|---|---|
@cacheControl | How long a response lives and who shares it | Lifetime from the schema, scopes |
@vary | What a named scope partitions on | Scopes in the schema |
@surrogateKey | Which surrogate keys a response carries | Keys from the schema |
@auth, @graphpilotPolicy | Nothing: parsed and reported, not enforced |
No finding on this page fails a deployment. Each is recorded on the deployment as a
diagnostic, shown in the portal under Schema →
Findings, printed by gpilot deploy, and returned by the GraphPilot
API.
@cacheControl
Section titled “@cacheControl”directive @cacheControl( maxAge: Int scope: CacheControlScope inheritMaxAge: Boolean swr: Int) on OBJECT | INTERFACE | UNION | FIELD_DEFINITION
enum CacheControlScope { PUBLIC PRIVATE}| Argument | Type | Default | Meaning |
|---|---|---|---|
maxAge | Int | The default of the maxAge argument in your definition, else 0 | The freshness lifetime in seconds. An explicit maxAge always wins over inheritance, on the type or the field. |
scope | CacheControlScope | PUBLIC | Who may be served the entry: PUBLIC, PRIVATE, or a named scope you declare on the enum. Omitted means PUBLIC, as in Apollo Server. |
inheritMaxAge | Boolean | false | On a non-root field with no maxAge, take the parent field’s effective maxAge instead of the default. No effect on a root field. |
swr | Int | none | Seconds past maxAge an entry may still be served as STALE while a fresh copy is fetched. A field that states no swr does not switch the window off for the query. See stale windows. |
| Location | Effect |
|---|---|
OBJECT, INTERFACE, UNION | Applies to every field that returns this type, unless that field has its own hint. On a member of a union or an implementor of an interface, the scope also joins every field returning that union or interface; its maxAge does not. See a field returning a union or interface. |
FIELD_ | Applies to that field only. Each argument it sets overrides the return type’s hint; each one it leaves out, scope included, comes from the type. See a field hint overrides only the arguments it sets. |
The default maxAge
Section titled “The default maxAge”The fallback for an unannotated root or composite field (Apollo’s defaultMaxAge) is the
default value you declare for the maxAge argument on the directive itself:
directive @cacheControl( maxAge: Int = 30 # unannotated composite fields fall back to 30, not 0 scope: CacheControlScope inheritMaxAge: Boolean swr: Int) on OBJECT | INTERFACE | UNION | FIELD_DEFINITIONmaxAge: Int with no default leaves the fallback at 0, which is uncacheable, as in Apollo Server.
A non-integer default is ignored and also leaves it at 0.
What the build reports
Section titled “What the build reports”| Written | Reported as | Effect |
|---|---|---|
@cacheControl used without a matching directive definition | Error | The hints are still resolved. |
| On any other location (a scalar, an enum, an input type or field) | Error | The hint is dropped. |
| On a mutation field | Warning | The hint is dropped; a mutation is never cached. |
An argument name other than maxAge, scope, inheritMaxAge and swr (or your configured names) | Error | The whole hint is dropped, and the build names the argument, so a typo such as maxage is reported rather than passing in silence. |
An argument of the wrong type: a maxAge or swr that is not a whole number from 0 to 4294967295, an inheritMaxAge that is not a Boolean, a scope that is not an enum value | Error | The whole hint is dropped. |
A scope value other than PUBLIC and PRIVATE that carries no @vary, or whose @vary is malformed | Error | The hint is kept with the scope read as PRIVATE, the narrowest audience it could have meant. |
A field returning a union or an interface whose members state a lower maxAge than the field resolves to | Warning | Nothing changes: the response is cached for the field’s maxAge. The warning names the field and each member below it. A field that inherits its parent’s maxAge is not compared. |
Declares a named scope: a value of your own CacheControlScope enum that partitions on the vary
rules it lists.
directive @vary(names: [String!]!) on ENUM_VALUE
enum CacheControlScope { PUBLIC PRIVATE ORGANIZATION @vary(names: ["x-org"]) ORGANIZATION_ROLE @vary(names: ["x-org", "x-role"])}| Argument | Type | Meaning |
|---|---|---|
names | [String!]! | Header names, matched case-insensitively against the name of your [[cache. entries. |
| Rule | Detail |
|---|---|
| Location | ENUM_VALUE, on the enum your scope argument declares. PUBLIC and PRIVATE take no @vary: their meaning is built in, and one written on either is ignored. |
A malformed names | Missing, not a list of strings, or empty: an error, and every scope naming that value is read as PRIVATE. |
A name with no [[cache. entry | Not a deployment failure. Every response in that scope is refused (CACHE_), and the build reports it at the annotation. See what the deployment build reports. |
A name whose [[cache. entry is not a legal header name | The same: the entry is skipped, every response in that scope is refused, and the build reports it at the annotation. |
A name in the gp- namespace | The build reports it at the annotation, and the deployment does not fail. With a [[cache. entry of that name, the scope still partitions on it: GraphPilot strips the namespace from what the client sends, then derives the entry’s value. Without one, it is refused like any name with no entry. Rename it outside the namespace anyway: the namespace is also stripped before your origin, so the entry’s pass_ has no effect. |
| Composition | The names of every scope a query selects are unioned. See how scopes compose. |
@surrogateKey
Section titled “@surrogateKey”directive @surrogateKey( static: String of: String ignore: Boolean) repeatable on SCALAR | OBJECT | FIELD_DEFINITION | INTERFACE | UNION | ENUMThe definition may be made more restrictive (String to String!, for example), not extended to
new types or locations. A bare @surrogateKey, with no argument, marks a field, scalar or enum as
a key field. Examples for every location are on keys from the
schema.
| Argument | Type | Meaning |
|---|---|---|
static | String | One or more fixed keys, separated by whitespace. See a group you purge by hand. |
of | String | The fields that form the key, as a GraphQL selection set ("user { id }") or in path syntax ("user.id"). Several fields form a composite key; the two notations can be mixed. |
ignore | Boolean | true switches the automatic key rules off where the directive sits. See below. |
| Rule | Detail |
|---|---|
| Leaf selections only | Each path ends on a scalar, an enum, or a list of these ([String!], [Role!]), never on an object, interface or union. |
| Fields without required arguments | A field whose arguments all have defaults, or that has none. |
| No globs or recursion | *, ** and recursive selections are not supported; each path names a field of the enclosing type. |
| Only where a traversal is possible | On an object, interface or union. Elsewhere the argument is ignored. |
| A nullable path | Allowed, and reported as a warning: an object whose value on that path is null is still tagged, with an empty value (Book:isbn: for @surrogateKey(of: "isbn") on Book), so it cannot be purged by identity. A path is nullable when any step of it is: author.id is null whenever author is. |
ignore
Section titled “ignore”| Written on | Effect |
|---|---|
An OBJECT or INTERFACE | No field of that type is keyed by the automatic rules. |
| A field of an object type or interface | That one field is not keyed by the automatic rules. |
A SCALAR, ENUM or UNION | Nothing. The build reports a warning. |
| Rule | Detail |
|---|---|
| Only the automatic rules | An exclusion never removes a key you wrote. @surrogateKey(ignore: true) alone is the opt-out and nothing else; it is not read as a bare @surrogateKey. |
| Beside an explicit key | @surrogateKey(ignore: true, of: "slug") swaps the automatic keys for the one written, with no warning. |
| Interfaces | An interface’s exclusion does not reach the types implementing it. |
| Values | Only true switches anything off. false is the same as leaving the argument out; any other value is a schema error and the automatic keys stay. |
| Against the configuration | When surrogate_key. or ignored_ excludes something a directive keys explicitly, the directive wins and the build reports a warning. |
When a directive produces no key
Section titled “When a directive produces no key”The build refuses the directive, with the type, the directive and its line and column, and the deployment still succeeds:
- an
ofnaming a field that does not exist, or a path that does not end in a scalar, enum or list thereof; - an argument of the wrong type;
- a bare
@surrogateKeyon a type that needs a selection; - a directive written where no key can come from, such as inside
extend typeor on a field argument.
Renaming directives
Section titled “Renaming directives”Each directive can be read under a name of your own, configured in your service configuration.
The keys and their rules are on [surrogate_key] and
[cache_control].
| Directive and argument | Renamed by |
|---|---|
@cacheControl | [[cache_control. directive_ |
maxAge, scope, inheritMaxAge, swr | max_age_arg, scope_arg, inherit_, swr_arg |
@vary and its names | vary_, vary_ |
@surrogateKey | [[surrogate_key. directive_ |
static, of, ignore | static_arg, of_arg, ignore_arg |
To read Apollo Federation’s @key(fields:) as a surrogate key directive, for example:
[[surrogate_key.directives]]directive_name = "key"of_arg = "fields"@auth and @graphpilotPolicy
Section titled “@auth and @graphpilotPolicy”directive @graphpilotPolicy(dsl: String) on ARGUMENT_DEFINITION | ENUM_VALUE@graphpilotPolicy(dsl:) attaches a policy expression to a
directive argument or to an enum value, so that a directive such as @auth(requires: [Role!]) can
state access rules in the schema.