UNPKG

@mercuriusjs/federation

Version:
213 lines (163 loc) 5.74 kB
# @mercuriusjs/federation A module to add Apollo Federation v1 metadata info to a schema. ## Quick start ```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 }) ``` ### Build a schema and pass it to `mercurius` 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 }) ... ``` ## API ### mercuriusFederationPlugin 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 }) ``` #### options 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. ### buildFederationSchema 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' ### federationSchemaTransformer 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`. #### Usage with `mercuriusFederationPlugin` 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] }) ``` #### Usage with `buildFederationSchema` and `mercurius` 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, { [MapperKind.FIELD]: (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] }) ```