Skip to content

Commit

Permalink
update plugin docs
Browse files Browse the repository at this point in the history
  • Loading branch information
hayes committed Apr 10, 2023
1 parent c73d889 commit d60e834
Show file tree
Hide file tree
Showing 9 changed files with 279 additions and 182 deletions.
90 changes: 89 additions & 1 deletion examples/prisma-federation/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -164,7 +164,10 @@ ReviewType.implement({

```typescript
// Use new `toSubGraphSchema` method to add subGraph specific types and queries to the schema
const schema = builder.toSubGraphSchema({});
const schema = builder.toSubGraphSchema({
// defaults to v2.3
linkUrl: 'https://specs.apollo.dev/federation/v2.3',
});

const server = new ApolloServer({
schema,
Expand All @@ -181,3 +184,88 @@ startStandaloneServer(server, { listen: { port: 4000 } })

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](https://github.com/hayes/pothos/tree/main/packages/plugin-federation/tests/example)

### Printing the schema

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.

### Field directives directives

Several federation directives can be configured directly when defining a field includes
`@shareable`, `@tag`, `@inaccessible`, and `@override`.

```ts
t.field({
type: 'String',
shareable: true,
tag: ['someTag'],
inaccessible: true,
override: { from: 'users' },
});
```

For more details on these directives, see the official Federation documentation.

### interface entities and @interfaceObject

Federation 2.3 introduces new features for federating interface definitions.

You can now pass interfaces to `asEntity` to defined keys for an interface:

```ts
const Media = builder.interfaceRef<{ id: string }>('Media').implement({
fields: (t) => ({
id: t.exposeID('id'),
...
}),
});

builder.asEntity(Media, {
key: builder.selection<{ id: string }>('id'),
resolveReference: ({ id }) => loadMediaById(id),
});
```

You can also extend interfaces from another subGraph by creating an `interfaceObject`:

```ts
const Media = builder.objectRef<{ id: string }>('Media').implement({
fields: (t) => ({
id: t.exposeID('id'),
// add new MediaFields here that are available on all implementors of the `Media` type
}),
});

builder.asEntity(Media, {
interfaceObject: true,
key: builder.selection<{ id: string }>('id'),
resolveReference: (ref) => ref,
});
```

See federation documentation for more details on `interfaceObject`s

### composeDirective

You can apply the `composeDirective` directive when building the subgraph schema:

```ts
export const schema = builder.toSubGraphSchema({
// This adds the @composeDirective directive
composeDirectives: ['@custom'],
// composeDirective requires an @link directive on the schema pointing the the url for your directive
schemaDirectives: {
link: { url: 'https://myspecs.dev/myCustomDirective/v1.0', import: ['@custom'] },
},
// You currently also need to provide an actual implementation for your Directive
directives: [
new GraphQLDirective({
locations: [DirectiveLocation.OBJECT, DirectiveLocation.INTERFACE],
name: 'custom',
}),
],
});
```
2 changes: 1 addition & 1 deletion packages/plugin-federation/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -248,7 +248,7 @@ builder.asEntity(Media, {

See federation documentation for more details on `interfaceObject`s

### composeDirective =
### composeDirective

You can apply the `composeDirective` directive when building the subgraph schema:

Expand Down
6 changes: 3 additions & 3 deletions packages/plugin-prisma-utils/README.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# Prisma utils for Pothos

This package is highly experimental and not recommended for production use
<Alert>This package is highly experimental and not recommended for production use</Alert>

The plugin adds new helpers for creating prisma compatible input types. It is NOT required to use
the normal prisma plugin.
Expand Down Expand Up @@ -65,7 +65,7 @@ operations).

```typescript
const StringFilter = builder.prismaFilter('String', {
ops: ['contains', 'equals', 'startsWith', 'not', 'equals'],
ops: ['contains', 'equals', 'startsWith', 'not'],
});

export const IDFilter = builder.prismaFilter('Int', {
Expand Down Expand Up @@ -268,7 +268,7 @@ There are 2 main approaches:
2. Dynamic Generation: Types are generated dynamically at runtime through helpers imported from your
App

### Static generator:
### Static generator

You can find an
[example static generator here](https://github.com/hayes/pothos/blob/main/packages/plugin-prisma-utils/tests/examples/codegen/generator.ts)
Expand Down
176 changes: 90 additions & 86 deletions packages/plugin-relay/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -17,24 +17,19 @@ yarn add @pothos/plugin-relay
import RelayPlugin from '@pothos/plugin-relay';
const builder = new SchemaBuilder({
plugins: [RelayPlugin],
relayOptions: {
// These will become the defaults in the next major version
clientMutationId: 'omit',
cursorType: 'String',
},
relay: {},
});
```

### Options

The `relayOptions` object passed to builder can contain the following properties:
The `relay` options object passed to builder can contain the following properties:

- `idFieldName`: The name of the field that contains the global id for the node. Defaults to `id`.
- `idFieldOptions`: Options to pass to the id field.
- `clientMutationId`: `required` (default) | `omit` | `optional`. Determines if clientMutationId
- `clientMutationId`: `omit` (default) | `required` | `optional`. Determines if clientMutationId
fields are created on `relayMutationFields`, and if they are required.
- `cursorType`: `String` | `ID`. Determines type used for cursor fields. Defaults behavior due to
legacy reasons is `String` for everything except for connection arguments which use `ID`.
- `cursorType`: `String` | `ID`. Determines type used for cursor fields. Defaults to `String`
Overwriting this default is highly encouraged.
- `nodeQueryOptions`: Options for the `node` field on the query object, set to false to omit the
field
Expand Down Expand Up @@ -64,6 +59,85 @@ The `relayOptions` object passed to builder can contain the following properties
- `defaultMutationInputTypeOptions`: default options for the mutation `Input` types.
- `nodesOnConnection`: If true, the `nodes` field will be added to the `Connection` object types.
- `defaultConnectionFieldOptions`: Default options for connection fields defined with t.connection
- `brandLoadedObjects`: Defaults to `true`. This will add a hidden symbol to objects returned from
the `load` methods of Nodes that allows the default `resolveType` implementation to identify the
type of the node. When this is enabled, you will not need to implement an `isTypeOf` check for
most common patterns.

### Creating Nodes

To create objects that extend the `Node` interface, you can use the new `builder.node` method.

```typescript
// Using object refs
const User = builder.objectRef<UserType>('User');
// Or using a class
class User {
id: string;
name: string;
}

builder.node(User, {
// define an id field
id: {
resolve: (user) => user.id,
// other options for id field can be added here
},

// Define only one of the following methods for loading nodes by id
loadOne: (id) => loadUserByID(id),
loadMany: (ids) => loadUsers(ids),
loadWithoutCache: (id) => loadUserByID(id),
loadManyWithoutCache: (ids) => loadUsers(ids),

// if using a class instaed of a ref, you will need to provide a name
name: 'User',
fields: (t) => ({
name: t.exposeString('name'),
}),
});
```

`builder.node` will create an object type that implements the `Node` interface. It will also create
the `Node` interface the first time it is used. The `resolve` function for `id` should return a
number or string, which will be converted to a globalID. The relay plugin adds to new query fields
`node` and `nodes` which can be used to directly fetch nodes using global IDs by calling the
provided `loadOne` or `loadMany` method. Each node will only be loaded once by id, and cached if the
same node is loaded multiple times inn the same request. You can provide `loadWithoutCache` or
`loadManyWithoutCache` instead if caching is not desired, or you are already using a caching
datasource like a dataloader.

Nodes may also implement an `isTypeOf` method which can be used to resolve the correct type for
lists of generic nodes. When using a class as the type parameter, the `isTypeOf` method defaults to
using an `instanceof` check, and falls back to checking the constructor property on the prototype.
The means that for many cases if you are using classes in your type parameters, and all your values
are instances of those classes, you won't need to implement an `isTypeOf` method, but it is usually
better to explicitly define that behavior.

By default (unless `brandLoadedObjects` is set to `false`) any nodes loaded through one of the
`load*` methods will be branded so that the default `resolveType` method can identify the GraphQL
type for the loaded object. This means `isTypeOf` is only required for `union` and `interface`
fields that return node objects that are manually loaded, where the union or interface does not have
a custom `resolveType` method that knows how to resolve the node type.

#### parsing node ids

By default all node ids are parsed as string. This behavior can be customized by providing a custom
parse function for your node's ID field:

```ts
const User = builder.objectRef<UserType>('User')
builder.node(User, {
// define an id field
id: {
resolve: (user) => user.id,
parse: (id) => Number.parseInt(id, 10),
},
// the ID is now a number
loadOne: (id) => loadUserByID(id),
...
});
```

### Global IDs

Expand All @@ -76,7 +150,7 @@ import { encodeGlobalID } from '@pothos/plugin-relay';
builder.queryFields((t) => ({
singleID: t.globalID({
resolve: (parent, args, context) => {
return encodeGlobalID('SomeType', 123);
return { id: 123, type: 'SomeType' };
},
}),
listOfIDs: t.globalIDList({
Expand Down Expand Up @@ -138,76 +212,6 @@ builder.queryType({
});
```

### Creating Nodes

To create objects that extend the `Node` interface, you can use the new `builder.node` method.

```typescript
class NumberThing {
id: number;

binary: string;

constructor(n: number) {
this.id = n;
this.binary = n.toString(2);
}
}

builder.node(NumberThing, {
// define an id field
id: {
resolve: (num) => num.id,
// other options for id field can be added here
},

// Define only one of the following methods for loading nodes by id
loadOne: (id) => new NumberThing(parseInt(id)),
loadMany: (ids) => ids.map((id) => new NumberThing(parseInt(id))),
loadWithoutCache: (id) => new NumberThing(parseInt(id)),
loadManyWithoutCache: (ids) => ids.map((id) => new NumberThing(parseInt(id))),

name: 'Number',
fields: (t) => ({
binary: t.exposeString('binary', {}),
}),
});
```

`builder.node` will create an object type that implements the `Node` interface. It will also create
the `Node` interface the first time it is used. The `resolve` function for `id` should return a
number or string, which will be converted to a globalID. The relay plugin adds to new query fields
`node` and `nodes` which can be used to directly fetch nodes using global IDs by calling the
provided `loadOne` or `loadMany` method. Each node will only be loaded once by id, and cached if the
same node is loaded multiple times inn the same request. You can provide `loadWithoutCache` or
`loadManyWithoutCache` instead if caching is not desired, or you are already using a caching
datasource like a dataloader.

Nodes may also implement an `isTypeOf` method which can be used to resolve the correct type for
lists of generic nodes. When using a class as the type parameter, the `isTypeOf` method defaults to
using an `instanceof` check, and falls back to checking the constructor property on the prototype.
The means that for many cases if you are using classes in your type parameters, and all your values
are instances of those classes, you won't need to implement an `isTypeOf` method, but it is usually
better to explicitly define that behavior.

#### parsing node ids

By default all node ids are parsed as string. This behavior can be customized by providing a custom
parse function for your node's ID field:

```ts
builder.node(NumberThing, {
// define an id field
id: {
resolve: (num) => num.id,
parse: (id) => Number.parseInt(id, 10),
},
// the ID is now a number
loadOne: (id) => new NumberThing(id),
...
});
```

### Creating Connections

The `t.connection` field builder method can be used to define connections. This method will
Expand Down Expand Up @@ -237,17 +241,17 @@ builder.queryFields((t) => ({
cursor: 'def',
node: new NumberThing(123),
},
]
}
}),
],
};
},
},
{
name: 'NameOfConnectionType', // optional, will use ParentObject + capitalize(FieldName) + "Connection" as the default
fields: (tc) => ({
// define extra fields on Connection
// We need to use a new variable for the connection field builder (eg tc) to get the correct types
}),
edgesField: {} // optional, allows customizing the edges field on the Connection Object
edgesField: {}, // optional, allows customizing the edges field on the Connection Object
// Other options for connection object can be added here
},
{
Expand All @@ -257,7 +261,7 @@ builder.queryFields((t) => ({
// define extra fields on Edge
// We need to use a new variable for the connection field builder (eg te) to get the correct types
}),
nodeField: {} // optional, allows customizing the node field on the Edge Object
nodeField: {}, // optional, allows customizing the node field on the Edge Object
},
),
}));
Expand All @@ -273,7 +277,7 @@ import { resolveOffsetConnection } from '@pothos/plugin-relay';

builder.queryFields((t) => ({
things: t.connection({
type: SomeThings,
type: SomeThing,
resolve: (parent, args) => {
return resolveOffsetConnection({ args }, ({ limit, offset }) => {
return getThings(offset, limit);
Expand Down
4 changes: 2 additions & 2 deletions website/components/Docs/Alert.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,15 +4,15 @@ import { ExclamationTriangleIcon } from '@heroicons/react/24/solid';

export interface AlertProps extends HTMLProps<HTMLElement> {}

export default function Alert({ children, title }: AlertProps) {
export default function Alert({ children }: AlertProps) {
return (
<div className="bg-yellow-50 dark:bg-[#282a36] border-l-4 border-yellow-400 p-4">
<div className="flex">
<div className="flex-shrink-0">
<ExclamationTriangleIcon className="h-5 w-5 text-yellow-400" aria-hidden="true" />
</div>
<div className="ml-3">
<p className="text-sm text-yellow-700 -my-4">{children}</p>
<p className="text-sm text-yellow-700 my-0">{children}</p>
</div>
</div>
</div>
Expand Down
Loading

0 comments on commit d60e834

Please sign in to comment.