Gathering detailed insights and metrics for @pothos-examples/prisma-federation
Gathering detailed insights and metrics for @pothos-examples/prisma-federation
Gathering detailed insights and metrics for @pothos-examples/prisma-federation
Gathering detailed insights and metrics for @pothos-examples/prisma-federation
npm install @pothos-examples/prisma-federation
Typescript
Module System
Node Version
NPM Version
Cumulative downloads
Total Downloads
Last Day
0%
NaN
Compared to previous day
Last Week
0%
NaN
Compared to previous week
Last Month
0%
NaN
Compared to previous month
Last Year
0%
NaN
Compared to previous year
A plugin for building subGraphs that are compatible with Apollo Federation 2.
This page will describe the basics of the Pothos API for federation, but will not cover detailed information on how federation works, or what all the terms on this page mean. For more general information on federation, see the official docs
You will need to install the plugin, as well as the directives plugin (@pothos/plugin-directives
)
and @apollo/subgraph
1yarn add @pothos/plugin-federation @pothos/plugin-directives @apollo/subgraph
You will likely want to install @apollo/server as well, but it is not required if you want to use a different server
1yarn add @apollo/server
1import DirectivePlugin from '@pothos/plugin-directives'; 2import FederationPlugin from '@pothos/plugin-federation'; 3const builder = new SchemaBuilder({ 4 // If you are using other plugins, the federation plugin should be listed after plugins like auth that wrap resolvers 5 plugins: [DirectivePlugin, FederationPlugin], 6});
Defining entities for you schema is a 2 step process. First you will need to define an object type
as you would normally, then you can convert that object type to an entity by providing a key
(or
keys
), and a method to load that entity.
1const UserType = builder.objectRef<User>('User').implement({ 2 fields: (t) => ({ 3 id: t.exposeID('id'), 4 name: t.exposeString('name'), 5 username: t.exposeString('username'), 6 }), 7}); 8 9builder.asEntity(UserType, { 10 key: builder.selection<{ id: string }>('id'), 11 resolveReference: (user, users) => users.find(({ id }) => user.id === id), 12});
keys
are defined using builder.selection
. This method MUST be called with a generic argument
that defines the types for any fields that are part of the key. key
may also be an array.
resolveReference
will be called with the type used by the key
selection.
Entities are Object types that may be extended with or returned by fields in other services.
builder.asEntity
describes how the Entity will be loaded when used by another services. The key
select (or selection) should use the types of scalars your server will produce for inputs. For
example, Apollo server will convert all ID fields to string
s, even if resolvers in other services
returns IDs as numbers.
External entities can be extended by calling builder.externalRef
, and then calling implement on
the returned ref.
builder.externalRef
takes the name of the entity, a selection (using builder.selection
, just
like a key
on an entity object), and a resolve method that loads an object given a key
. The
return type of the resolver is used as the backing type for the ref, and will be the type of the
parent
arg when defining fields for this type. The key
also describes what fields will be
selected from another service to use as the parent
object in resolvers for fields added when
implementing the externalRef
.
1const ProductRef = builder.externalRef( 2 'Product', 3 builder.selection<{ upc: string }>('upc'), 4 (entity) => { 5 const product = inventory.find(({ upc }) => upc === entity.upc); 6 7 // extends the entity ({upc: string}) with other product details available in this service 8 return product && { ...entity, ...product }; 9 }, 10); 11 12ProductRef.implement({ 13 // Additional external fields can be defined here which can be used by `requires` or `provides` directives 14 externalFields: (t) => ({ 15 price: t.float(), 16 weight: t.float(), 17 }), 18 fields: (t) => ({ 19 // exposes properties added during loading of the entity above 20 upc: t.exposeString('upc'), 21 inStock: t.exposeBoolean('inStock'), 22 shippingEstimate: t.float({ 23 // fields can add a `requires` directive for any of the externalFields defined above 24 // which will be made available as part of the first arg in the resolver. 25 requires: builder.selection<{ weight?: number; price?: number }>('price weight'), 26 resolve: (data) => { 27 // free for expensive items 28 if ((data.price ?? 0) > 1000) { 29 return 0; 30 } 31 // estimate is based on weight 32 return (data.weight ?? 0) * 0.5; 33 }, 34 }), 35 }), 36});
To add a @provides
directive, you will need to implement the Parent type of the field being
provided as an external ref, and then use the .provides
method of the returned ref when defining
the field that will have the @provides
directive. The provided field must be listed as an
externalField
in the external type.
1const UserType = builder.externalRef('User', builder.selection<{ id: string }>('id')).implement({ 2 externalFields: (t) => ({ 3 // The field that will be provided 4 username: t.string(), 5 }), 6 fields: (t) => ({ 7 id: t.exposeID('id'), 8 }), 9}); 10 11const ReviewType = builder.objectRef<Review>('Review'); 12ReviewType.implement({ 13 fields: (t) => ({ 14 id: t.exposeID('id'), 15 body: t.exposeString('body'), 16 author: t.field({ 17 // using UserType.provides<...>(...) instead of just UserType adds the provide annotations 18 // and ensures the resolved value includes data for the provided field 19 // The generic in Type.provides works the same as the `builder.selection` method. 20 type: UserType.provides<{ username: string }>('username'), 21 resolve: (review) => ({ 22 id: review.authorID, 23 username: usernames.find((username) => username.id === review.authorID)!.username, 24 }), 25 }), 26 product: t.field({ 27 type: Product, 28 resolve: (review) => ({ upc: review.product.upc }), 29 }), 30 }), 31});
1// Use new `toSubGraphSchema` method to add subGraph specific types and queries to the schema 2const schema = builder.toSubGraphSchema({ 3 // defaults to v2.3 4 linkUrl: 'https://specs.apollo.dev/federation/v2.3', 5}); 6 7const server = new ApolloServer({ 8 schema, 9}); 10 11startStandaloneServer(server, { listen: { port: 4000 } }) 12 .then(({ url }) => { 13 console.log(`🚀 Server ready at ${url}`); 14 }) 15 .catch((error) => { 16 throw error; 17 });
For a functional example that combines multiple graphs built with Pothos into a single schema see https://github.com/hayes/pothos/tree/main/packages/plugin-federation/tests/example
If you are printing the schema as a string for any reason, and then using the printed schema for
Apollo Federation(submitting if using Managed Federation, or composing manually with rover
), you
must use printSubgraphSchema
(from @apollo/subgraph
) or another compatible way of printing the
schema(that includes directives) in order for it to work.
Several federation directives can be configured directly when defining a field includes
@shareable
, @tag
, @inaccessible
, and @override
.
1t.field({ 2 type: 'String', 3 shareable: true, 4 tag: ['someTag'], 5 inaccessible: true, 6 override: { from: 'users' }, 7});
For more details on these directives, see the official Federation documentation.
Federation 2.3 introduces new features for federating interface definitions.
You can now pass interfaces to asEntity
to defined keys for an interface:
1const Media = builder.interfaceRef<{ id: string }>('Media').implement({ 2 fields: (t) => ({ 3 id: t.exposeID('id'), 4 ... 5 }), 6}); 7 8builder.asEntity(Media, { 9 key: builder.selection<{ id: string }>('id'), 10 resolveReference: ({ id }) => loadMediaById(id), 11});
You can also extend interfaces from another subGraph by creating an interfaceObject
:
1const Media = builder.objectRef<{ id: string }>('Media').implement({
2 fields: (t) => ({
3 id: t.exposeID('id'),
4 // add new MediaFields here that are available on all implementors of the `Media` type
5 }),
6});
7
8builder.asEntity(Media, {
9 interfaceObject: true,
10 key: builder.selection<{ id: string }>('id'),
11 resolveReference: (ref) => ref,
12});
See federation documentation for more details on interfaceObject
s
You can apply the composeDirective
directive when building the subgraph schema:
1export const schema = builder.toSubGraphSchema({
2 // This adds the @composeDirective directive
3 composeDirectives: ['@custom'],
4 // composeDirective requires an @link directive on the schema pointing the the url for your directive
5 schemaDirectives: {
6 link: { url: 'https://myspecs.dev/myCustomDirective/v1.0', import: ['@custom'] },
7 },
8 // You currently also need to provide an actual implementation for your Directive
9 directives: [
10 new GraphQLDirective({
11 locations: [DirectiveLocation.OBJECT, DirectiveLocation.INTERFACE],
12 name: 'custom',
13 }),
14 ],
15});
No vulnerabilities found.
No security vulnerabilities found.