@mercuriusjs/federation
Version:
A plugin for mercurius federation
213 lines (163 loc) • 5.74 kB
Markdown
A module to add Apollo Federation v1 metadata info to a schema.
```bash
npm i fastify @mercuriusjs/federation
```
```js
const Fastify = require('fastify')
const { mercuriusFederationPlugin } = require('@mercuriusjs/federation')
const users = {
1: {
id: '1',
name: 'John',
username: '@john'
},
2: {
id: '2',
name: 'Jane',
username: '@jane'
}
}
const app = Fastify()
const schema = `
extend type Query {
me: User
}
type User @key(fields: "id") {
id: ID!
name: String
username: String
}
`
const resolvers = {
Query: {
me: () => {
return users['1']
}
},
User: {
__resolveReference: (source, args, context, info) => {
return users[source.id]
}
}
}
app.register(mercuriusFederationPlugin, {
schema,
resolvers
})
app.get('/', async function (req, reply) {
const query = '{ _service { sdl } }'
return app.graphql(query)
})
app.listen({ port: 3000 })
```
Instead of using the plugin, the federation schema can be built using the `buildFederationSchema` function and passing the schema generated to `mercurius`.
```javascript
const Fastify = require('fastify')
const mercurius = require('mercurius')
const { buildFederationSchema } = require('../')
...
app.register(mercurius, {
schema: buildFederationSchema(schema),
resolvers,
graphiql: true
})
...
```
A fastify plugin to create a `mercurius` server that expose the `federation` directives.
```javascript
const { mercuriusFederationPlugin } = require('@mercuriusjs/federation')
const schema = ...
const resolvers = ...
const app = Fastify()
app.register(mercuriusFederationPlugin, {
schema,
resolvers
})
```
Uses the same [options](https://mercurius.dev/#/docs/api/options?id=plugin-options) of `mercurius` but
it requires a `string`, `DocumentNode` or an Array of `DocumentNode` for `schema` attribute.
Create a schema object that can be used in a federated environment
`(schema, opts) => GraphQLSchema`
- `schema` string | DocumentNode | Array<DocumentNode>: the source schema
- `opts` object:
- `isGateway` boolean: If enabled create a schema compatible with the `gateway`, Default 'false'
Wraps an array of schema transformers (e.g. custom directive transformers built with `@graphql-tools/utils` `mapSchema`) so that the `resolveReference` functions defined on entity types are preserved after the transformation.
When mercurius applies `schemaTransforms`, transformers such as `mapSchema` recreate `GraphQLObjectType` instances via `new GraphQLObjectType(type.toConfig())`. Because `resolveReference` is a non-standard property set at runtime by mercurius, it is **lost** during this process. `federationSchemaTransformer` takes care of copying it back onto the new type instances after every transformer runs.
`(transformers: SchemaTransformer[]) => SchemaTransformer`
- `transformers` Array of functions `(schema: GraphQLSchema) => GraphQLSchema`: the directive / schema transformers to apply.
> **Note:** `mercuriusFederationPlugin` already wraps `schemaTransforms` with `federationSchemaTransformer` automatically. You only need to use this function when you build the federation schema yourself with `buildFederationSchema` and register it directly with `mercurius`.
When using the plugin, just pass your transformers directly — the plugin handles the wrapping:
```js
const { mercuriusFederationPlugin } = require('@mercuriusjs/federation')
app.register(mercuriusFederationPlugin, {
schema,
resolvers,
schemaTransforms: [upperDirectiveTransformer]
})
```
When registering mercurius directly with a federation schema, you must wrap transformers with `federationSchemaTransformer` to preserve `resolveReference`:
```js
const mercurius = require('mercurius')
const { buildFederationSchema, federationSchemaTransformer } = require('@mercuriusjs/federation')
const { MapperKind, mapSchema, getDirective } = require('@graphql-tools/utils')
const { defaultFieldResolver } = require('graphql')
// Define a directive transformer
function upperDirectiveTransformer (schema) {
return mapSchema(schema, {
[]: (fieldConfig) => {
const upperDirective = getDirective(schema, fieldConfig, 'upper')?.[0]
if (upperDirective) {
const { resolve = defaultFieldResolver } = fieldConfig
fieldConfig.resolve = async function (obj, args, ctx, info) {
const result = await resolve(obj, args, ctx, info)
return typeof result === 'string' ? result.toUpperCase() : result
}
return fieldConfig
}
}
})
}
const schema = `
directive @upper on FIELD_DEFINITION
extend type Query {
me: User
}
type User @key(fields: "id") {
id: ID!
name: String @upper
username: String
}
`
const resolvers = {
Query: {
me: () => users['1']
},
User: {
__resolveReference: (source) => users[source.id]
}
}
app.register(mercurius, {
schema: buildFederationSchema(schema),
resolvers,
schemaTransforms: federationSchemaTransformer([upperDirectiveTransformer])
})
```
**Without `federationSchemaTransformer`** in this scenario, `_entities` queries will fail because `resolveReference` is lost during the schema transformation:
```js
// ⚠️ _entities queries will return null for entity fields
app.register(mercurius, {
schema: buildFederationSchema(schema),
resolvers,
schemaTransforms: [upperDirectiveTransformer]
})
```