Huge News!Announcing our $40M Series B led by Abstract Ventures.Learn More
Socket
Sign inDemoInstall
Socket

@pothos/plugin-relay

Package Overview
Dependencies
Maintainers
1
Versions
81
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

@pothos/plugin-relay - npm Package Compare versions

Comparing version 3.5.2 to 3.6.0

31

CHANGELOG.md
# Change Log
## 3.6.0
### Minor Changes
- 8add0378: Allow relay.\*.FieldOptions objects to set `nullable` to change nullability of default
relay fields
- 5294a17f: Explicitly make `pageInfo` non nullable. Previously `pageInfo` was nullable for
`defaultFieldNullability: true`, which is against the Relay spec. You can revert back to previous
behavior by updating your builder relay options:
```
relay: {
pageInfoFieldOptions: {
nullable: true,
},
},
```
- 8add0378: Explicitly make `id` field on Node interface nullable
- 8add0378: Explicitly make `cursor` non nullable. Previously `cursor` was nullable for
`defaultFieldNullability: true`, which is against the Relay spec. You can revert back to previous
behavior by updating your builder relay options:
```
relay: {
cursorFieldOptions: {
nullable: true,
},
},
```
## 3.5.2

@@ -4,0 +35,0 @@

29

esm/schema-builder.js

@@ -19,18 +19,18 @@ import SchemaBuilder, { createContextCache, getTypeBrand, verifyRef, } from '@pothos/core';

hasNextPage: t.exposeBoolean("hasNextPage", {
nullable: false,
...hasNextPageFieldOptions,
nullable: false,
}),
hasPreviousPage: t.exposeBoolean("hasPreviousPage", {
nullable: false,
...hasPreviousPageFieldOptions,
nullable: false,
}),
startCursor: t.expose("startCursor", {
nullable: true,
...startCursorFieldOptions,
type: cursorType,
nullable: true,
}),
endCursor: t.expose("endCursor", {
nullable: true,
...endCursorFieldOptions,
type: cursorType,
nullable: true,
}),

@@ -51,2 +51,3 @@ }),

id: t.globalID({
nullable: false,
resolve: (parent) => {

@@ -59,2 +60,3 @@ throw new Error("id field not implemented");

this.queryField("node", (t) => t.field({
nullable: true,
...this.options.relayOptions.nodeQueryOptions,

@@ -65,6 +67,9 @@ type: ref,

},
nullable: true,
resolve: async (root, args, context, info) => (await resolveNodes(this, context, info, [String(args.id)]))[0],
}));
this.queryField("nodes", (t) => t.field({
nullable: {
list: false,
items: true,
},
...this.options.relayOptions.nodesQueryOptions,

@@ -75,6 +80,2 @@ type: [ref],

},
nullable: {
list: false,
items: true,
},
resolve: async (root, args, context, info) => (await resolveNodes(this, context, info, args.ids)),

@@ -138,4 +139,4 @@ }));

this.objectField(ref, "id", (t) => t.globalID({
nullable: false,
...options.id,
nullable: false,
args: {},

@@ -199,4 +200,4 @@ resolve: async (parent, args, context, info) => ({

clientMutationId: t.id({
nullable: this.options.relayOptions.clientMutationId === "optional",
...clientMutationIdFieldOptions,
nullable: this.options.relayOptions.clientMutationId === "optional",
resolve: (parent, args, context, info) => mutationIdCache(context).get(String(info.path.prev.key)),

@@ -239,2 +240,3 @@ }),

pageInfo: t.field({
nullable: false,
...pageInfoFieldOptions,

@@ -245,5 +247,5 @@ type: this.pageInfoRef(),

edges: t.field({
nullable: (edgesNullableField !== null && edgesNullableField !== void 0 ? edgesNullableField : edgesNullable),
...edgesFieldOptions,
type: [edgeRef],
nullable: (edgesNullableField !== null && edgesNullableField !== void 0 ? edgesNullableField : edgesNullable),
resolve: (parent) => parent.edges,

@@ -258,4 +260,4 @@ }),

node: t.field({
nullable: nodeNullableField !== null && nodeNullableField !== void 0 ? nodeNullableField : nodeNullable,
...nodeFieldOptions,
nullable: nodeNullableField !== null && nodeNullableField !== void 0 ? nodeNullableField : nodeNullable,
type,

@@ -265,2 +267,3 @@ resolve: (parent) => parent.node,

cursor: t.expose("cursor", {
nullable: false,
type: cursorType,

@@ -267,0 +270,0 @@ ...cursorFieldOptions,

@@ -9,31 +9,33 @@ import { GraphQLResolveInfo } from 'graphql';

pageInfoTypeOptions: Omit<PothosSchemaTypes.ObjectTypeOptions<Types, PageInfoShape>, 'fields'>;
nodeQueryOptions: Omit<PothosSchemaTypes.QueryFieldOptions<Types, OutputRefShape<GlobalIDShape<Types> | string>, true, {
nodeQueryOptions: Omit<PothosSchemaTypes.QueryFieldOptions<Types, OutputRefShape<GlobalIDShape<Types> | string>, boolean, {
id: InputFieldRef<InputShape<Types, 'ID'>>;
}, Promise<unknown>>, 'args' | 'nullable' | 'resolve' | 'type'>;
}, Promise<unknown>>, 'args' | 'resolve' | 'type'>;
nodesQueryOptions: Omit<PothosSchemaTypes.QueryFieldOptions<Types, [
OutputRefShape<GlobalIDShape<Types> | string>
], true, {
], FieldNullability<[unknown]>, {
ids: InputFieldRef<InputShape<Types, 'ID'>[]>;
}, Promise<unknown>[]>, 'args' | 'nullable' | 'resolve' | 'type'>;
mutationInputArgOptions: Omit<PothosSchemaTypes.ArgFieldOptions<Types, InputRef<{}>, true>, 'fields' | 'required' | 'type'>;
clientMutationIdInputOptions: Omit<PothosSchemaTypes.InputObjectFieldOptions<Types, 'ID', true>, 'required' | 'type'>;
clientMutationIdFieldOptions: Omit<PothosSchemaTypes.ObjectFieldOptions<Types, {}, 'ID', false, {}, Types['Scalars']['ID']['Output']>, 'args' | 'nullable' | 'resolve' | 'type'>;
cursorFieldOptions: Normalize<Omit<PothosSchemaTypes.ObjectFieldOptions<Types, {}, 'ID' | 'String', false, {}, Types['Scalars']['ID' | 'String']['Output']>, 'args' | 'nullable' | 'resolve' | 'type'> & {
}, Promise<unknown>[]>, 'args' | 'resolve' | 'type'>;
mutationInputArgOptions: Omit<PothosSchemaTypes.ArgFieldOptions<Types, InputRef<{}>, boolean>, 'fields' | 'type'>;
clientMutationIdInputOptions: Omit<PothosSchemaTypes.InputObjectFieldOptions<Types, 'ID', boolean>, 'type'>;
clientMutationIdFieldOptions: Omit<PothosSchemaTypes.ObjectFieldOptions<Types, {}, 'ID', boolean, {}, Types['Scalars']['ID']['Output']>, 'args' | 'resolve' | 'type'>;
cursorFieldOptions: Normalize<Omit<PothosSchemaTypes.ObjectFieldOptions<Types, {}, 'ID' | 'String', false, {}, Types['Scalars']['ID' | 'String']['Output']>, 'args' | 'resolve' | 'type'> & {
type?: 'ID' | 'String';
}>;
nodeFieldOptions: Omit<PothosSchemaTypes.ObjectFieldOptions<Types, {}, ObjectRef<{}>, false, {}, GlobalIDShape<Types> | string>, 'args' | 'nullable' | 'resolve' | 'type'> & {
nodeFieldOptions: Omit<PothosSchemaTypes.ObjectFieldOptions<Types, {}, ObjectRef<{}>, Types['DefaultNodeNullability'], {}, GlobalIDShape<Types> | string>, 'args' | 'nullable' | 'resolve' | 'type'> & {
nullable?: Types['DefaultNodeNullability'];
};
edgesFieldOptions: Omit<PothosSchemaTypes.ObjectFieldOptions<Types, {}, [ObjectRef<{}>], false, {}, unknown[]>, 'args' | 'nullable' | 'resolve' | 'type'> & {
edgesFieldOptions: Omit<PothosSchemaTypes.ObjectFieldOptions<Types, {}, [
ObjectRef<{}>
], Types['DefaultEdgesNullability'], {}, unknown[]>, 'args' | 'resolve' | 'nullable' | 'type'> & {
nullable?: Types['DefaultEdgesNullability'];
};
pageInfoFieldOptions: Omit<PothosSchemaTypes.ObjectFieldOptions<Types, {}, OutputRef<PageInfoShape>, false, {}, PageInfoShape>, 'args' | 'nullable' | 'resolve' | 'type'>;
hasNextPageFieldOptions: Omit<PothosSchemaTypes.ObjectFieldOptions<Types, PageInfoShape, 'Boolean', false, {}, boolean>, 'args' | 'nullable' | 'resolve' | 'type'>;
hasPreviousPageFieldOptions: Omit<PothosSchemaTypes.ObjectFieldOptions<Types, PageInfoShape, 'Boolean', false, {}, boolean>, 'args' | 'nullable' | 'resolve' | 'type'>;
startCursorFieldOptions: Omit<PothosSchemaTypes.ObjectFieldOptions<Types, PageInfoShape, 'ID' | 'String', false, {}, string | null>, 'args' | 'nullable' | 'resolve' | 'type'>;
endCursorFieldOptions: Omit<PothosSchemaTypes.ObjectFieldOptions<Types, PageInfoShape, 'ID' | 'String', false, {}, string | null>, 'args' | 'nullable' | 'resolve' | 'type'>;
beforeArgOptions: Omit<PothosSchemaTypes.InputObjectFieldOptions<Types, 'ID' | 'String', false>, 'required' | 'type'>;
afterArgOptions: Omit<PothosSchemaTypes.InputObjectFieldOptions<Types, 'ID' | 'String', false>, 'required' | 'type'>;
firstArgOptions: Omit<PothosSchemaTypes.InputObjectFieldOptions<Types, 'Int', false>, 'required' | 'type'>;
lastArgOptions: Omit<PothosSchemaTypes.InputObjectFieldOptions<Types, 'Int', false>, 'required' | 'type'>;
pageInfoFieldOptions: Omit<PothosSchemaTypes.ObjectFieldOptions<Types, {}, OutputRef<PageInfoShape>, boolean, {}, PageInfoShape>, 'args' | 'resolve' | 'type'>;
hasNextPageFieldOptions: Omit<PothosSchemaTypes.ObjectFieldOptions<Types, PageInfoShape, 'Boolean', boolean, {}, boolean>, 'args' | 'resolve' | 'type'>;
hasPreviousPageFieldOptions: Omit<PothosSchemaTypes.ObjectFieldOptions<Types, PageInfoShape, 'Boolean', boolean, {}, boolean>, 'args' | 'resolve' | 'type'>;
startCursorFieldOptions: Omit<PothosSchemaTypes.ObjectFieldOptions<Types, PageInfoShape, 'ID' | 'String', boolean, {}, string | null>, 'args' | 'resolve' | 'type'>;
endCursorFieldOptions: Omit<PothosSchemaTypes.ObjectFieldOptions<Types, PageInfoShape, 'ID' | 'String', boolean, {}, string | null>, 'args' | 'resolve' | 'type'>;
beforeArgOptions: Omit<PothosSchemaTypes.InputObjectFieldOptions<Types, 'ID' | 'String', boolean>, 'required' | 'type'>;
afterArgOptions: Omit<PothosSchemaTypes.InputObjectFieldOptions<Types, 'ID' | 'String', boolean>, 'required' | 'type'>;
firstArgOptions: Omit<PothosSchemaTypes.InputObjectFieldOptions<Types, 'Int', boolean>, 'required' | 'type'>;
lastArgOptions: Omit<PothosSchemaTypes.InputObjectFieldOptions<Types, 'Int', boolean>, 'required' | 'type'>;
encodeGlobalID?: (typename: string, id: bigint | number | string) => string;

@@ -40,0 +42,0 @@ decodeGlobalID?: (globalID: string) => {

"use strict";
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
Object.defineProperty(o, k2, { enumerable: true, get: function() { return m[k]; } });
var desc = Object.getOwnPropertyDescriptor(m, k);
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
desc = { enumerable: true, get: function() { return m[k]; } };
}
Object.defineProperty(o, k2, desc);
}) : (function(o, m, k, k2) {

@@ -6,0 +10,0 @@ if (k2 === undefined) k2 = k;

"use strict";
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
Object.defineProperty(o, k2, { enumerable: true, get: function() { return m[k]; } });
var desc = Object.getOwnPropertyDescriptor(m, k);
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
desc = { enumerable: true, get: function() { return m[k]; } };
}
Object.defineProperty(o, k2, desc);
}) : (function(o, m, k, k2) {

@@ -41,18 +45,18 @@ if (k2 === undefined) k2 = k;

hasNextPage: t.exposeBoolean('hasNextPage', {
nullable: false,
...hasNextPageFieldOptions,
nullable: false,
}),
hasPreviousPage: t.exposeBoolean('hasPreviousPage', {
nullable: false,
...hasPreviousPageFieldOptions,
nullable: false,
}),
startCursor: t.expose('startCursor', {
nullable: true,
...startCursorFieldOptions,
type: cursorType,
nullable: true,
}),
endCursor: t.expose('endCursor', {
nullable: true,
...endCursorFieldOptions,
type: cursorType,
nullable: true,
}),

@@ -73,2 +77,3 @@ }),

id: t.globalID({
nullable: false,
resolve: (parent) => {

@@ -81,2 +86,3 @@ throw new Error('id field not implemented');

this.queryField('node', (t) => t.field({
nullable: true,
...this.options.relayOptions.nodeQueryOptions,

@@ -87,6 +93,9 @@ type: ref,

},
nullable: true,
resolve: async (root, args, context, info) => (await (0, utils_1.resolveNodes)(this, context, info, [String(args.id)]))[0],
}));
this.queryField('nodes', (t) => t.field({
nullable: {
list: false,
items: true,
},
...this.options.relayOptions.nodesQueryOptions,

@@ -97,6 +106,2 @@ type: [ref],

},
nullable: {
list: false,
items: true,
},
resolve: async (root, args, context, info) => (await (0, utils_1.resolveNodes)(this, context, info, args.ids)),

@@ -160,4 +165,4 @@ }));

this.objectField(ref, 'id', (t) => t.globalID({
nullable: false,
...options.id,
nullable: false,
args: {},

@@ -221,4 +226,4 @@ resolve: async (parent, args, context, info) => ({

clientMutationId: t.id({
nullable: this.options.relayOptions.clientMutationId === 'optional',
...clientMutationIdFieldOptions,
nullable: this.options.relayOptions.clientMutationId === 'optional',
resolve: (parent, args, context, info) => mutationIdCache(context).get(String(info.path.prev.key)),

@@ -261,2 +266,3 @@ }),

pageInfo: t.field({
nullable: false,
...pageInfoFieldOptions,

@@ -267,5 +273,5 @@ type: this.pageInfoRef(),

edges: t.field({
nullable: (edgesNullableField !== null && edgesNullableField !== void 0 ? edgesNullableField : edgesNullable),
...edgesFieldOptions,
type: [edgeRef],
nullable: (edgesNullableField !== null && edgesNullableField !== void 0 ? edgesNullableField : edgesNullable),
resolve: (parent) => parent.edges,

@@ -280,4 +286,4 @@ }),

node: t.field({
nullable: nodeNullableField !== null && nodeNullableField !== void 0 ? nodeNullableField : nodeNullable,
...nodeFieldOptions,
nullable: nodeNullableField !== null && nodeNullableField !== void 0 ? nodeNullableField : nodeNullable,
type,

@@ -287,2 +293,3 @@ resolve: (parent) => parent.node,

cursor: t.expose('cursor', {
nullable: false,
type: cursorType,

@@ -289,0 +296,0 @@ ...cursorFieldOptions,

@@ -9,31 +9,33 @@ import { GraphQLResolveInfo } from 'graphql';

pageInfoTypeOptions: Omit<PothosSchemaTypes.ObjectTypeOptions<Types, PageInfoShape>, 'fields'>;
nodeQueryOptions: Omit<PothosSchemaTypes.QueryFieldOptions<Types, OutputRefShape<GlobalIDShape<Types> | string>, true, {
nodeQueryOptions: Omit<PothosSchemaTypes.QueryFieldOptions<Types, OutputRefShape<GlobalIDShape<Types> | string>, boolean, {
id: InputFieldRef<InputShape<Types, 'ID'>>;
}, Promise<unknown>>, 'args' | 'nullable' | 'resolve' | 'type'>;
}, Promise<unknown>>, 'args' | 'resolve' | 'type'>;
nodesQueryOptions: Omit<PothosSchemaTypes.QueryFieldOptions<Types, [
OutputRefShape<GlobalIDShape<Types> | string>
], true, {
], FieldNullability<[unknown]>, {
ids: InputFieldRef<InputShape<Types, 'ID'>[]>;
}, Promise<unknown>[]>, 'args' | 'nullable' | 'resolve' | 'type'>;
mutationInputArgOptions: Omit<PothosSchemaTypes.ArgFieldOptions<Types, InputRef<{}>, true>, 'fields' | 'required' | 'type'>;
clientMutationIdInputOptions: Omit<PothosSchemaTypes.InputObjectFieldOptions<Types, 'ID', true>, 'required' | 'type'>;
clientMutationIdFieldOptions: Omit<PothosSchemaTypes.ObjectFieldOptions<Types, {}, 'ID', false, {}, Types['Scalars']['ID']['Output']>, 'args' | 'nullable' | 'resolve' | 'type'>;
cursorFieldOptions: Normalize<Omit<PothosSchemaTypes.ObjectFieldOptions<Types, {}, 'ID' | 'String', false, {}, Types['Scalars']['ID' | 'String']['Output']>, 'args' | 'nullable' | 'resolve' | 'type'> & {
}, Promise<unknown>[]>, 'args' | 'resolve' | 'type'>;
mutationInputArgOptions: Omit<PothosSchemaTypes.ArgFieldOptions<Types, InputRef<{}>, boolean>, 'fields' | 'type'>;
clientMutationIdInputOptions: Omit<PothosSchemaTypes.InputObjectFieldOptions<Types, 'ID', boolean>, 'type'>;
clientMutationIdFieldOptions: Omit<PothosSchemaTypes.ObjectFieldOptions<Types, {}, 'ID', boolean, {}, Types['Scalars']['ID']['Output']>, 'args' | 'resolve' | 'type'>;
cursorFieldOptions: Normalize<Omit<PothosSchemaTypes.ObjectFieldOptions<Types, {}, 'ID' | 'String', false, {}, Types['Scalars']['ID' | 'String']['Output']>, 'args' | 'resolve' | 'type'> & {
type?: 'ID' | 'String';
}>;
nodeFieldOptions: Omit<PothosSchemaTypes.ObjectFieldOptions<Types, {}, ObjectRef<{}>, false, {}, GlobalIDShape<Types> | string>, 'args' | 'nullable' | 'resolve' | 'type'> & {
nodeFieldOptions: Omit<PothosSchemaTypes.ObjectFieldOptions<Types, {}, ObjectRef<{}>, Types['DefaultNodeNullability'], {}, GlobalIDShape<Types> | string>, 'args' | 'nullable' | 'resolve' | 'type'> & {
nullable?: Types['DefaultNodeNullability'];
};
edgesFieldOptions: Omit<PothosSchemaTypes.ObjectFieldOptions<Types, {}, [ObjectRef<{}>], false, {}, unknown[]>, 'args' | 'nullable' | 'resolve' | 'type'> & {
edgesFieldOptions: Omit<PothosSchemaTypes.ObjectFieldOptions<Types, {}, [
ObjectRef<{}>
], Types['DefaultEdgesNullability'], {}, unknown[]>, 'args' | 'resolve' | 'nullable' | 'type'> & {
nullable?: Types['DefaultEdgesNullability'];
};
pageInfoFieldOptions: Omit<PothosSchemaTypes.ObjectFieldOptions<Types, {}, OutputRef<PageInfoShape>, false, {}, PageInfoShape>, 'args' | 'nullable' | 'resolve' | 'type'>;
hasNextPageFieldOptions: Omit<PothosSchemaTypes.ObjectFieldOptions<Types, PageInfoShape, 'Boolean', false, {}, boolean>, 'args' | 'nullable' | 'resolve' | 'type'>;
hasPreviousPageFieldOptions: Omit<PothosSchemaTypes.ObjectFieldOptions<Types, PageInfoShape, 'Boolean', false, {}, boolean>, 'args' | 'nullable' | 'resolve' | 'type'>;
startCursorFieldOptions: Omit<PothosSchemaTypes.ObjectFieldOptions<Types, PageInfoShape, 'ID' | 'String', false, {}, string | null>, 'args' | 'nullable' | 'resolve' | 'type'>;
endCursorFieldOptions: Omit<PothosSchemaTypes.ObjectFieldOptions<Types, PageInfoShape, 'ID' | 'String', false, {}, string | null>, 'args' | 'nullable' | 'resolve' | 'type'>;
beforeArgOptions: Omit<PothosSchemaTypes.InputObjectFieldOptions<Types, 'ID' | 'String', false>, 'required' | 'type'>;
afterArgOptions: Omit<PothosSchemaTypes.InputObjectFieldOptions<Types, 'ID' | 'String', false>, 'required' | 'type'>;
firstArgOptions: Omit<PothosSchemaTypes.InputObjectFieldOptions<Types, 'Int', false>, 'required' | 'type'>;
lastArgOptions: Omit<PothosSchemaTypes.InputObjectFieldOptions<Types, 'Int', false>, 'required' | 'type'>;
pageInfoFieldOptions: Omit<PothosSchemaTypes.ObjectFieldOptions<Types, {}, OutputRef<PageInfoShape>, boolean, {}, PageInfoShape>, 'args' | 'resolve' | 'type'>;
hasNextPageFieldOptions: Omit<PothosSchemaTypes.ObjectFieldOptions<Types, PageInfoShape, 'Boolean', boolean, {}, boolean>, 'args' | 'resolve' | 'type'>;
hasPreviousPageFieldOptions: Omit<PothosSchemaTypes.ObjectFieldOptions<Types, PageInfoShape, 'Boolean', boolean, {}, boolean>, 'args' | 'resolve' | 'type'>;
startCursorFieldOptions: Omit<PothosSchemaTypes.ObjectFieldOptions<Types, PageInfoShape, 'ID' | 'String', boolean, {}, string | null>, 'args' | 'resolve' | 'type'>;
endCursorFieldOptions: Omit<PothosSchemaTypes.ObjectFieldOptions<Types, PageInfoShape, 'ID' | 'String', boolean, {}, string | null>, 'args' | 'resolve' | 'type'>;
beforeArgOptions: Omit<PothosSchemaTypes.InputObjectFieldOptions<Types, 'ID' | 'String', boolean>, 'required' | 'type'>;
afterArgOptions: Omit<PothosSchemaTypes.InputObjectFieldOptions<Types, 'ID' | 'String', boolean>, 'required' | 'type'>;
firstArgOptions: Omit<PothosSchemaTypes.InputObjectFieldOptions<Types, 'Int', boolean>, 'required' | 'type'>;
lastArgOptions: Omit<PothosSchemaTypes.InputObjectFieldOptions<Types, 'Int', boolean>, 'required' | 'type'>;
encodeGlobalID?: (typename: string, id: bigint | number | string) => string;

@@ -40,0 +42,0 @@ decodeGlobalID?: (globalID: string) => {

"use strict";
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
Object.defineProperty(o, k2, { enumerable: true, get: function() { return m[k]; } });
var desc = Object.getOwnPropertyDescriptor(m, k);
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
desc = { enumerable: true, get: function() { return m[k]; } };
}
Object.defineProperty(o, k2, desc);
}) : (function(o, m, k, k2) {

@@ -6,0 +10,0 @@ if (k2 === undefined) k2 = k;

{
"name": "@pothos/plugin-relay",
"version": "3.5.2",
"version": "3.6.0",
"description": "A Pothos plugin for adding relay style connections, nodes, and cursor based pagination to your GraphQL schema",

@@ -38,3 +38,3 @@ "main": "./lib/index.js",

"devDependencies": {
"@pothos/core": "3.3.2",
"@pothos/core": "3.3.3",
"@pothos/test-utils": "1.0.2",

@@ -54,3 +54,3 @@ "graphql": "16.3.0",

},
"readme": "# Relay Plugin\n\nThe Relay plugin adds a number of builder methods a helper functions to simplify building a relay\ncompatible schema.\n\n## Usage\n\n### Install\n\n```bash\nyarn add @pothos/plugin-relay\n```\n\n### Setup\n\n```typescript\nimport RelayPlugin from '@pothos/plugin-relay';\nconst builder = new SchemaBuilder({\n plugins: [RelayPlugin],\n relayOptions: {\n clientMutationId: 'omit',\n cursorType: 'String',\n },\n});\n```\n\n### Options\n\nThe `relayOptions` object passed to builder can contain the following properties:\n\n- `clientMutationId`: `required` (default) | `omit` | `optional`. Determins if clientMutationId\n fields are created on connections, and if they are required.\n- `cursorType`: `String` | `ID`. Determines type used for cursor fields. Defaults behavior due to\n legacy reasons is `String` for everything except for connection arguments which use `ID`.\n Overwritting this default is hightly encouraged.\n- `nodeQueryOptions`: Options for the `node` field on the query object\n- `nodesQueryOptions`: Options for the `nodes` field on the query object\n- `nodeTypeOptions`: Options for the `Node` interface type\n- `pageInfoTypeOptions`: Options for the `TypeInfo` object type\n- `clientMutationIdFieldOptions`: Options for the `clientMutationId` field on connection objects\n- `clientMutationIdInputOptions`: Options for the `clientMutationId` input field on connections\n fields\n- `mutationInputArgOptions`: Options for the Input object created for each connection field\n- `cursorFieldOptions`: Options for the `cursor` field on an edge object.\n- `nodeFieldOptions`: Options for the `node` field on an edge object.\n- `edgesFieldOptions`: Options for the `edges` field on a connection object.\n- `pageInfoFieldOptions`: Options for the `pageInfo` field on a connection object.\n- `hasNextPageFieldOptions`: Options for the `hasNextPage` field on the `PageInfo` object.\n- `hasPreviousPageFieldOptions`: Options for the `hasPreviousPage` field on the `PageInfo` object.\n- `startCursorFieldOptions`: Options for the `startCursor` field on the `PageInfo` object.\n- `endCursorFieldOptions`: Options for the `endCursor` field on the `PageInfo` object.\n- `beforeArgOptions`: Options for the `before` arg on a connection field.\n- `afterArgOptions`: Options for the `after` arg on a connection field.\n- `firstArgOptions`: Options for the `first` arg on a connection field.\n- `lastArgOptions`: Options for the `last` arg on a connection field.\n\n### Global IDs\n\nTo make it easier to create globally unique ids the relay plugin adds new methods for creating\nglobalID fields.\n\n```typescript\nimport { encodeGlobalID } from '@pothos/plugin-relay';\n\nbuilder.queryFields((t) => ({\n singleID: t.globalID({\n resolve: (parent, args, context) => {\n return encodeGlobalID('SomeType', 123);\n },\n }),\n listOfIDs: t.globalIDList({\n resolve: (parent, args, context) => {\n return [{ id: 123, type: 'SomeType' }];\n },\n }),\n}));\n```\n\nThe returned IDs can either be a string \\(which is expected to already be a globalID\\), or an object\nwith the an `id` and a `type`, The type can be either the name of a name as a string, or any object\nthat can be used in a type parameter.\n\nThere are also new methods for adding globalIDs in arguments or fields of input types:\n\n```typescript\nbuilder.queryType({\n fields: (t) => ({\n fieldThatAcceptsGlobalID: t.boolean({\n args: {\n id: t.arg.globalID({\n required: true,\n }),\n idList: t.arg.globalIDList(),\n },\n resolve(parent, args) {\n console.log(`Get request for type ${args.id.type} with id ${args.id.typename}`);\n return true;\n },\n }),\n }),\n});\n```\n\nglobalIDs used in arguments expect the client to send a globalID string, but will automatically be\nconverted to an object with 2 properties (`id` and `typename`) before they are passed to your\nresolver in the arguments object.\n\n### Creating Nodes\n\nTo create objects that extend the `Node` interface, you can use the new `builder.node` method.\n\n```typescript\nclass NumberThing {\n id: number;\n\n binary: string;\n\n constructor(n: number) {\n this.id = n;\n this.binary = n.toString(2);\n }\n}\n\nbuilder.node(NumberThing, {\n id: {\n resolve: (num) => num.id,\n // other options for id field can be added here\n },\n loadOne: (id) => new NumberThing(parseInt(id)),\n loadMany: (ids) => ids.map((id) => new NumberThing(parseInt(id))),\n name: 'Number',\n fields: (t) => ({\n binary: t.exposeString('binary', {}),\n }),\n});\n```\n\n`builder.node` will create an object type that implements the `Node` interface. It will also create\nthe `Node` interface the first time it is used. The `resolve` function for `id` should return a\nnumber or string, which will be converted to a globalID. The `loadOne` and `loadMany` methods are\noptional, and `loadMany` will be used if both are present. These methods allow a nodes to be loaded\nby id. The relay plugin adds to new query fields `node` and `nodes` which can be used to directly\nfetch nodes using global IDs.\n\nNodes may also implement an `isTypeOf` method which can be used to resolve the correct type for\nlists of generic nodes. When using a class as the type parameter, the `isTypeOf` method defaults to\nusing an `instanceof` check, and falls back to checking the constructor property on the prototype.\nThe means that for many cases if you are using classes in your type parameters, and all your values\nare instances of those classes, you won't need to implement an `isTypeOf` method, but it is usually\nbetter to explicitly define that behavior.\n\n### Creating Connections\n\nThe `t.connection` field builder method can be used to define connections. This method will\nautomatically create the `Connection` and `Edge` objects used by the connection, and add `before`,\n`after`, `first`, and `last` arguments. The first time this method is used, it will also create the\n`PageInfo` type.\n\n```typescript\nbuilder.queryFields((t) => ({\n numbers: t.connection(\n {\n type: NumberThing,\n resolve: (parent, { first, last, before, after }) => {\n return resolveOffsetConnection({ args }, ({ limit, offset }) => {\n return {\n pageInfo: {\n hasNextPage: false,\n hasPreviousPage: false,\n startCursor: 'abc',\n endCursor: 'def',\n },\n edges: [\n {\n cursor: 'xyz',\n node: new NumberThing(123),\n },\n ],\n };\n });\n },\n },\n {\n name: 'NameOfConnectionType', // optional, will use ParentObject + capitalize(FieldName) + \"Connection\" as the default\n fields: (tc) => ({\n // define extra fields on Connection\n // We need to use a new variable for the connection field builder (eg tc) to get the correct types\n }),\n // Other options for connection object can be added here\n },\n {\n // Same as above, but for the Edge Object\n name: 'NameOfEdgeType', // optional, will use Connection name + \"Edge\" as the default\n fields: (te) => ({\n // define extra fields on Edge\n // We need to use a new variable for the connection field builder (eg te) to get the correct types\n }),\n },\n ),\n}));\n```\n\nManually implementing connections can be cumbersome, so there are a couple of helper methods that\ncan make resolving connections a little easier.\n\nFor limit/offset based apis:\n\n```typescript\nimport { resolveOffsetConnection } from '@pothos/plugin-relay';\n\nbuilder.queryFields((t) => ({\n numbers: t.connection({\n type: SomeThings,\n resolve: (parent, args) => {\n return resolveOffsetConnection({ args }, ({ limit, offset }) => {\n return getThings(offset, limit);\n });\n },\n }),\n}));\n```\n\n`resolveOffsetConnection` has a few default limits to prevent unintentionally allowing too many\nrecords to be fetched at once. These limits can be configure using the following options:\n\n```typescript\n{\n args: ConnectionArguments;\n defaultSize?: number; // defaults to 20\n maxSize?: number; // defaults to 100\n}\n```\n\nFor APIs where you have the full array available you can use `resolveArrayConnection`, which works\njust like `resolveOffsetConnection` and accepts the same options.\n\n```typescript\nimport { resolveArrayConnection } from '@pothos/plugin-relay';\n\nbuilder.queryFields((t) => ({\n numbers: t.connection({\n type: SomeThings,\n resolve: (parent, args) => {\n return resolveOffsetConnection({ args }, getAllTheThingsAsArray());\n },\n }),\n}));\n```\n\nI am planning to add more helpers in the future.\n\n### Relay Mutations\n\nYou can use the `relayMutationField` method to define relay compliant mutation fields. This method\nwill generate a mutation field, an input object with a `clientMutationId` field, and an output\nobject with the corresponding `clientMutationId`.\n\nExample ussage:\n\n```typescript\nbuilder.relayMutationField(\n 'deleteItem',\n {\n inputFields: (t) => ({\n id: t.id({\n required: true,\n }),\n }),\n },\n {\n resolve: async (root, args, ctx) => {\n if (ctx.items.has(args.input.id)) {\n ctx.items.delete(args.input.id);\n\n return { success: true };\n }\n\n return { sucess: false };\n },\n },\n {\n outputFields: (t) => ({\n sucess: t.boolean({\n resolve: (result) => result.success,\n }),\n }),\n },\n);\n```\n\nWhich produces the following graphql types:\n\n```graphql\ninput DeleteItemInput {\n clientMutationId: ID!\n id: ID!\n}\n\ntype DeleteItemPayload {\n clientMutationId: ID!\n itWorked: Boolean!\n}\n\ntype Mutation {\n deleteItem(input: DeleteItemInput!): DeleteItemPayload!\n}\n```\n\nThe `relayMutationField` has 4 arguments:\n\n- `name`: Name of the mutation field\n- `inputOptions`: Options for the `input` object\n- `fieldOptions`: Options for the mutation field\n- `payloadOptions`: Options for the Payload object\n\nThe `inputOptions` has a couple of non-standard options:\n\n- `name` which can be used to set the name of the input object\n- `argName` which can be used to overwrite the default arguments name (`input`).\n\nThe `payloadOptions` object also accepts a `name` property for setting the name of the payload\nobject.\n\nYou can also access refs for the created input and payload objects so you can re-use them in other\nfields:\n\n```typescript\n// Using aliases when destructuring lets you name your refs rather than using the generic `inputType` and `payloadType`\nconst { inputType: DeleteItemInput, payloadType: DeleteItemPayload } = builder.relayMutationField(\n 'deleteItem',\n ...\n);\n```\n\n### Reusing connection objects\n\nIn some cases you may want to create a connection object type that is shared by multiple fields. To\ndo this, you will need to create the connection object separately and then create a fields using a\nref to your connection object:\n\n```typescript\nimport { resolveOffsetConnection } from '@pothos/plugin-relay';\n\nconst ThingsConnection = builder.connectionObject(\n {\n // connection optionss\n type: SomeThing,\n name: 'ThingsConnection',\n },\n {\n // Edge options (optional)\n name: 'ThingsEdge', // defaults to Appending `Edge` to the Connection name\n },\n);\n\nbuilder.queryFields((t) => ({\n things: t.connection({\n type: ThingsConnection,\n args: {\n ...t.arg.connectionArgs(),\n },\n resolve: (parent, args) => {\n return resolveOffsetConnection({ args }, ({ limit, offset }) => {\n return getThings(offset, limit);\n });\n },\n }),\n}));\n```\n\n`builder.connectionObject` creates the connect object type and the associated Edge type.\n`t.arg.connectionArgs()` will create the default connection args.\n\n### Expose nodes\n\nThe `t.node` and `t.nodes` methods can be used to add additional node fields. the expected return\nvalues of `id` and `ids` fields is the same as the resolve value of `t.globalID`, and can either be\na globalID or an object with and an `id` and a `type`.\n\nLoading nodes by `id` uses a request cache, so the same node will only be loaded once per request,\neven if it is used multiple times across the schema.\n\n```typescript\nbuilder.queryFields((t) => ({\n extraNode: t.node({\n id: () => 'TnVtYmVyOjI=',\n }),\n moreNodes: t.nodeList({\n ids: () => ['TnVtYmVyOjI=', { id: 10, type: 'SomeType' }],\n }),\n}));\n```\n\n### decoding and encoding global ids\n\nThe relay plugin exports `decodeGlobalID` and `encodeGlobalID` as helper methods for interacting\nwith global IDs directly. If you accept a global ID as an argument you can use the `decodeGlobalID`\nfunction to decode it:\n\n```typescript\nbuilder.mutationFields((t) => ({\n updateThing: t.field({\n type: Thing,\n args: {\n id: t.args.id({ required: true }),\n update: t.args.string({ required: true }),\n },\n resolve(parent, args) {\n const { type, id } = decodeGlobalId(args.id);\n\n const thing = Thing.findById(id);\n\n thing.update(args.update);\n\n return thing;\n },\n }),\n}));\n```\n\n### Using custom encoding for global ids\n\nIn some cases you may want to encode global ids differently than the build in ID encoding. To do\nthis, you can pass a custom encoding and decoding function into the relay options of the builder:\n\n```typescript\nimport '@pothos/plugin-relay';\nconst builder = new SchemaBuilder({\n plugins: [RelayPlugin],\n relayOptions: {\n encodeGlobalID: (typename: string, id: string | number | bigint) => `${typename}:${id}`,\n decodeGlobalID: (globalID: string) => {\n const [typename, id] = globalID.split(':');\n\n return { typename, id };\n },\n },\n});\n```\n\n### Extending all connections\n\nThere are 2 builder methods for adding fields to all connection objects: `t.globalConnectionField`\nand `t.globalConnectionFields`. These methods work like many of the other methods on the builder for\nadding fields to objects or interfaces.\n\n```typescript\nbuilder.globalConnectionField('totalCount', (t) =>\n t.int({\n nullable: false,\n resolve: (parent) => 123,\n }),\n);\n// Or\nbuilder.globalConnectionFields((t) => ({\n totalCount: t.int({\n nullable: false,\n resolve: (parent) => 123,\n }),\n}));\n```\n\nIn the above example, we are just returning a static nubmer for our `totalCount` field. To make this\nmore useful, we need to have our resolvers for each connection actually return an object that\ncontains a totalCount for us. To guarantee that resolvers correclty implement this behavior, we can\ndefine custom properties that must be returned from connection resolvers when we set up our builder:\n\n```typescript\nimport RelayPlugin from '@pothos/plugin-relay';\nconst builder = new SchemaBuilder<{\n Connection: {\n totalCount: number;\n };\n}>({\n plugins: [RelayPlugin],\n relayOptions: {},\n});\n```\n\nNow typescript will ensure that objects returned from each connection resolver include a totalCount\nproperty, which we can use in our connection fields:\n\n```typescript\nbuilder.globalConnectionField('totalCount', (t) =>\n t.int({\n nullable: false,\n resolve: (parent) => parent.totalCount,\n }),\n);\n```\n\nNote that adding additional required properties will make it harder to use the provided connection\nhelpers since they will not automatically return your custom properties. You will need to manually\nadd in any custom props after gettig the result from the helpers:\n\n```typescript\nbuilder.queryFields((t) => ({\n posts: t.connection({\n type: Post,\n resolve: (parent, args, context) => {\n const postsArray = context.Posts.getAll();\n const result = resolveArrayConnection({ args }, postsArray);\n\n return result && { totalCount: postsArray.length, ...result };\n },\n }),\n}));\n```\n"
"readme": "# Relay Plugin\n\nThe Relay plugin adds a number of builder methods a helper functions to simplify building a relay\ncompatible schema.\n\n## Usage\n\n### Install\n\n```bash\nyarn add @pothos/plugin-relay\n```\n\n### Setup\n\n```typescript\nimport RelayPlugin from '@pothos/plugin-relay';\nconst builder = new SchemaBuilder({\n plugins: [RelayPlugin],\n relayOptions: {\n // These will become the defaults in the next major version\n clientMutationId: 'omit',\n cursorType: 'String',\n },\n});\n```\n\n### Options\n\nThe `relayOptions` object passed to builder can contain the following properties:\n\n- `clientMutationId`: `required` (default) | `omit` | `optional`. Determins if clientMutationId\n fields are created on `relayMutationFields`, and if they are required.\n- `cursorType`: `String` | `ID`. Determines type used for cursor fields. Defaults behavior due to\n legacy reasons is `String` for everything except for connection arguments which use `ID`.\n Overwritting this default is hightly encouraged.\n- `nodeQueryOptions`: Options for the `node` field on the query object\n- `nodesQueryOptions`: Options for the `nodes` field on the query object\n- `nodeTypeOptions`: Options for the `Node` interface type\n- `pageInfoTypeOptions`: Options for the `TypeInfo` object type\n- `clientMutationIdFieldOptions`: Options for the `clientMutationId` field on connection objects\n- `clientMutationIdInputOptions`: Options for the `clientMutationId` input field on connections\n fields\n- `mutationInputArgOptions`: Options for the Input object created for each connection field\n- `cursorFieldOptions`: Options for the `cursor` field on an edge object.\n- `nodeFieldOptions`: Options for the `node` field on an edge object.\n- `edgesFieldOptions`: Options for the `edges` field on a connection object.\n- `pageInfoFieldOptions`: Options for the `pageInfo` field on a connection object.\n- `hasNextPageFieldOptions`: Options for the `hasNextPage` field on the `PageInfo` object.\n- `hasPreviousPageFieldOptions`: Options for the `hasPreviousPage` field on the `PageInfo` object.\n- `startCursorFieldOptions`: Options for the `startCursor` field on the `PageInfo` object.\n- `endCursorFieldOptions`: Options for the `endCursor` field on the `PageInfo` object.\n- `beforeArgOptions`: Options for the `before` arg on a connection field.\n- `afterArgOptions`: Options for the `after` arg on a connection field.\n- `firstArgOptions`: Options for the `first` arg on a connection field.\n- `lastArgOptions`: Options for the `last` arg on a connection field.\n\n### Global IDs\n\nTo make it easier to create globally unique ids the relay plugin adds new methods for creating\nglobalID fields.\n\n```typescript\nimport { encodeGlobalID } from '@pothos/plugin-relay';\n\nbuilder.queryFields((t) => ({\n singleID: t.globalID({\n resolve: (parent, args, context) => {\n return encodeGlobalID('SomeType', 123);\n },\n }),\n listOfIDs: t.globalIDList({\n resolve: (parent, args, context) => {\n return [{ id: 123, type: 'SomeType' }];\n },\n }),\n}));\n```\n\nThe returned IDs can either be a string \\(which is expected to already be a globalID\\), or an object\nwith the an `id` and a `type`, The type can be either the name of a name as a string, or any object\nthat can be used in a type parameter.\n\nThere are also new methods for adding globalIDs in arguments or fields of input types:\n\n```typescript\nbuilder.queryType({\n fields: (t) => ({\n fieldThatAcceptsGlobalID: t.boolean({\n args: {\n id: t.arg.globalID({\n required: true,\n }),\n idList: t.arg.globalIDList(),\n },\n resolve(parent, args) {\n console.log(`Get request for type ${args.id.type} with id ${args.id.typename}`);\n return true;\n },\n }),\n }),\n});\n```\n\nglobalIDs used in arguments expect the client to send a globalID string, but will automatically be\nconverted to an object with 2 properties (`id` and `typename`) before they are passed to your\nresolver in the arguments object.\n\n### Creating Nodes\n\nTo create objects that extend the `Node` interface, you can use the new `builder.node` method.\n\n```typescript\nclass NumberThing {\n id: number;\n\n binary: string;\n\n constructor(n: number) {\n this.id = n;\n this.binary = n.toString(2);\n }\n}\n\nbuilder.node(NumberThing, {\n // defiine an id field\n id: {\n resolve: (num) => num.id,\n // other options for id field can be added here\n },\n\n // Define only one of the following methods for loading nodes by id\n loadOne: (id) => new NumberThing(parseInt(id)),\n loadMany: (ids) => ids.map((id) => new NumberThing(parseInt(id))),\n loadWithoutCache: (id) => new NumberThing(parseInt(id)),\n loadManyWithoutCache: (ids) => ids.map((id) => new NumberThing(parseInt(id))),\n\n name: 'Number',\n fields: (t) => ({\n binary: t.exposeString('binary', {}),\n }),\n});\n```\n\n`builder.node` will create an object type that implements the `Node` interface. It will also create\nthe `Node` interface the first time it is used. The `resolve` function for `id` should return a\nnumber or string, which will be converted to a globalID. The relay plugin adds to new query fields\n`node` and `nodes` which can be used to directly fetch nodes using global IDs by calling the\nprovided `loadOne` or `laodMany` method. Each node will only be loaded once by id, and cached if the\nsame node is loaded multiple times inn the same request. You can provide `loadWithoutCache` or\n`loadManyWithoutCache` instead if caching is not desired, or you are already using a caching\ndatasource like a dataloader.\n\nNodes may also implement an `isTypeOf` method which can be used to resolve the correct type for\nlists of generic nodes. When using a class as the type parameter, the `isTypeOf` method defaults to\nusing an `instanceof` check, and falls back to checking the constructor property on the prototype.\nThe means that for many cases if you are using classes in your type parameters, and all your values\nare instances of those classes, you won't need to implement an `isTypeOf` method, but it is usually\nbetter to explicitly define that behavior.\n\n### Creating Connections\n\nThe `t.connection` field builder method can be used to define connections. This method will\nautomatically create the `Connection` and `Edge` objects used by the connection, and add `before`,\n`after`, `first`, and `last` arguments. The first time this method is used, it will also create the\n`PageInfo` type.\n\n```typescript\nbuilder.queryFields((t) => ({\n numbers: t.connection(\n {\n type: NumberThing,\n resolve: (parent, { first, last, before, after }) => {\n return resolveOffsetConnection({ args }, ({ limit, offset }) => {\n return {\n pageInfo: {\n hasNextPage: false,\n hasPreviousPage: false,\n startCursor: 'abc',\n endCursor: 'def',\n },\n edges: [\n {\n cursor: 'xyz',\n node: new NumberThing(123),\n },\n ],\n };\n });\n },\n },\n {\n name: 'NameOfConnectionType', // optional, will use ParentObject + capitalize(FieldName) + \"Connection\" as the default\n fields: (tc) => ({\n // define extra fields on Connection\n // We need to use a new variable for the connection field builder (eg tc) to get the correct types\n }),\n // Other options for connection object can be added here\n },\n {\n // Same as above, but for the Edge Object\n name: 'NameOfEdgeType', // optional, will use Connection name + \"Edge\" as the default\n fields: (te) => ({\n // define extra fields on Edge\n // We need to use a new variable for the connection field builder (eg te) to get the correct types\n }),\n },\n ),\n}));\n```\n\nManually implementing connections can be cumbersome, so there are a couple of helper methods that\ncan make resolving connections a little easier.\n\nFor limit/offset based apis:\n\n```typescript\nimport { resolveOffsetConnection } from '@pothos/plugin-relay';\n\nbuilder.queryFields((t) => ({\n things: t.connection({\n type: SomeThings,\n resolve: (parent, args) => {\n return resolveOffsetConnection({ args }, ({ limit, offset }) => {\n return getThings(offset, limit);\n });\n },\n }),\n}));\n```\n\n`resolveOffsetConnection` has a few default limits to prevent unintentionally allowing too many\nrecords to be fetched at once. These limits can be configure using the following options:\n\n```typescript\n{\n args: ConnectionArguments;\n defaultSize?: number; // defaults to 20\n maxSize?: number; // defaults to 100\n}\n```\n\nFor APIs where you have the full array available you can use `resolveArrayConnection`, which works\njust like `resolveOffsetConnection` and accepts the same options.\n\n```typescript\nimport { resolveArrayConnection } from '@pothos/plugin-relay';\n\nbuilder.queryFields((t) => ({\n things: t.connection({\n type: SomeThings,\n resolve: (parent, args) => {\n return resolveArrayConnection({ args }, getAllTheThingsAsArray());\n },\n }),\n}));\n```\n\nI am planning to add more helpers in the future.\n\n### Relay Mutations\n\nYou can use the `relayMutationField` method to define relay compliant mutation fields. This method\nwill generate a mutation field, an input object with a `clientMutationId` field, and an output\nobject with the corresponding `clientMutationId`.\n\nExample ussage:\n\n```typescript\nbuilder.relayMutationField(\n 'deleteItem',\n {\n inputFields: (t) => ({\n id: t.id({\n required: true,\n }),\n }),\n },\n {\n resolve: async (root, args, ctx) => {\n if (ctx.items.has(args.input.id)) {\n ctx.items.delete(args.input.id);\n\n return { success: true };\n }\n\n return { sucess: false };\n },\n },\n {\n outputFields: (t) => ({\n sucess: t.boolean({\n resolve: (result) => result.success,\n }),\n }),\n },\n);\n```\n\nWhich produces the following graphql types:\n\n```graphql\ninput DeleteItemInput {\n clientMutationId: ID!\n id: ID!\n}\n\ntype DeleteItemPayload {\n clientMutationId: ID!\n itWorked: Boolean!\n}\n\ntype Mutation {\n deleteItem(input: DeleteItemInput!): DeleteItemPayload!\n}\n```\n\nThe `relayMutationField` has 4 arguments:\n\n- `name`: Name of the mutation field\n- `inputOptions`: Options for the `input` object\n- `fieldOptions`: Options for the mutation field\n- `payloadOptions`: Options for the Payload object\n\nThe `inputOptions` has a couple of non-standard options:\n\n- `name` which can be used to set the name of the input object\n- `argName` which can be used to overwrite the default arguments name (`input`).\n\nThe `payloadOptions` object also accepts a `name` property for setting the name of the payload\nobject.\n\nYou can also access refs for the created input and payload objects so you can re-use them in other\nfields:\n\n```typescript\n// Using aliases when destructuring lets you name your refs rather than using the generic `inputType` and `payloadType`\nconst { inputType: DeleteItemInput, payloadType: DeleteItemPayload } = builder.relayMutationField(\n 'deleteItem',\n ...\n);\n```\n\n### Reusing connection objects\n\nIn some cases you may want to create a connection object type that is shared by multiple fields. To\ndo this, you will need to create the connection object separately and then create a fields using a\nref to your connection object:\n\n```typescript\nimport { resolveOffsetConnection } from '@pothos/plugin-relay';\n\nconst ThingsConnection = builder.connectionObject(\n {\n // connection options\n type: SomeThing,\n name: 'ThingsConnection',\n },\n {\n // Edge options (optional)\n name: 'ThingsEdge', // defaults to Appending `Edge` to the Connection name\n },\n);\n\nbuilder.queryFields((t) => ({\n things: t.connection({\n type: ThingsConnection,\n args: {\n ...t.arg.connectionArgs(),\n },\n resolve: (parent, args) => {\n return resolveOffsetConnection({ args }, ({ limit, offset }) => {\n return getThings(offset, limit);\n });\n },\n }),\n}));\n```\n\n`builder.connectionObject` creates the connect object type and the associated Edge type.\n`t.arg.connectionArgs()` will create the default connection args.\n\n### Expose nodes\n\nThe `t.node` and `t.nodes` methods can be used to add additional node fields. the expected return\nvalues of `id` and `ids` fields is the same as the resolve value of `t.globalID`, and can either be\na globalID or an object with and an `id` and a `type`.\n\nLoading nodes by `id` uses a request cache, so the same node will only be loaded once per request,\neven if it is used multiple times across the schema.\n\n```typescript\nbuilder.queryFields((t) => ({\n extraNode: t.node({\n id: () => 'TnVtYmVyOjI=',\n }),\n moreNodes: t.nodeList({\n ids: () => ['TnVtYmVyOjI=', { id: 10, type: 'SomeType' }],\n }),\n}));\n```\n\n### decoding and encoding global ids\n\nThe relay plugin exports `decodeGlobalID` and `encodeGlobalID` as helper methods for interacting\nwith global IDs directly. If you accept a global ID as an argument you can use the `decodeGlobalID`\nfunction to decode it:\n\n```typescript\nbuilder.mutationFields((t) => ({\n updateThing: t.field({\n type: Thing,\n args: {\n id: t.args.id({ required: true }),\n update: t.args.string({ required: true }),\n },\n resolve(parent, args) {\n const { type, id } = decodeGlobalId(args.id);\n\n const thing = Thing.findById(id);\n\n thing.update(args.update);\n\n return thing;\n },\n }),\n}));\n```\n\n### Using custom encoding for global ids\n\nIn some cases you may want to encode global ids differently than the build in ID encoding. To do\nthis, you can pass a custom encoding and decoding function into the relay options of the builder:\n\n```typescript\nimport RelayPlugin from '@pothos/plugin-relay';\nconst builder = new SchemaBuilder({\n plugins: [RelayPlugin],\n relayOptions: {\n encodeGlobalID: (typename: string, id: string | number | bigint) => `${typename}:${id}`,\n decodeGlobalID: (globalID: string) => {\n const [typename, id] = globalID.split(':');\n\n return { typename, id };\n },\n },\n});\n```\n\n### Extending all connections\n\nThere are 2 builder methods for adding fields to all connection objects: `t.globalConnectionField`\nand `t.globalConnectionFields`. These methods work like many of the other methods on the builder for\nadding fields to objects or interfaces.\n\n```typescript\nbuilder.globalConnectionField('totalCount', (t) =>\n t.int({\n nullable: false,\n resolve: (parent) => 123,\n }),\n);\n// Or\nbuilder.globalConnectionFields((t) => ({\n totalCount: t.int({\n nullable: false,\n resolve: (parent) => 123,\n }),\n}));\n```\n\nIn the above example, we are just returning a static nubmer for our `totalCount` field. To make this\nmore useful, we need to have our resolvers for each connection actually return an object that\ncontains a totalCount for us. To guarantee that resolvers correclty implement this behavior, we can\ndefine custom properties that must be returned from connection resolvers when we set up our builder:\n\n```typescript\nimport RelayPlugin from '@pothos/plugin-relay';\nconst builder = new SchemaBuilder<{\n Connection: {\n totalCount: number;\n };\n}>({\n plugins: [RelayPlugin],\n relayOptions: {},\n});\n```\n\nNow typescript will ensure that objects returned from each connection resolver include a totalCount\nproperty, which we can use in our connection fields:\n\n```typescript\nbuilder.globalConnectionField('totalCount', (t) =>\n t.int({\n nullable: false,\n resolve: (parent) => parent.totalCount,\n }),\n);\n```\n\nNote that adding additional required properties will make it harder to use the provided connection\nhelpers since they will not automatically return your custom properties. You will need to manually\nadd in any custom props after gettig the result from the helpers:\n\n```typescript\nbuilder.queryFields((t) => ({\n posts: t.connection({\n type: Post,\n resolve: (parent, args, context) => {\n const postsArray = context.Posts.getAll();\n const result = resolveArrayConnection({ args }, postsArray);\n\n return result && { totalCount: postsArray.length, ...result };\n },\n }),\n}));\n```\n\n### Changing nullability of edges and nodes\n\nIf you want to change the nullability of the `edges` field on a `Connection` or the `node` field on\nan `Edge` you can configure this in 2 ways:\n\n#### Globally\n\n```typescript\nimport RelayPlugin from '@pothos/plugin-relay';\nconst builder = new SchemaBuilder<{\n DefaultEdgesNullability: false;\n DefaultNodeNullability: true;\n}>({\n plugins: [RelayPlugin],\n relayOptions: {\n edgesFieldOptions: {\n nullable: false,\n },\n nodeFieldOptions: {\n nullable: true,\n },\n },\n});\n```\n\nThe types provided for `DefaultEdgesNullability` and `DefaultNodeNullability` must match the values\nprovided in the nullable option of `edgesFieldOptions` and `nodeFieldOptions` respectivly. This will\nset the default nullabilitty for all connections created by your builder.\n\nNullablity for `edges` fields defaults to `{ list: false, items: true }` and the nullablity of\n`node` fields default to `false`.\n\n#### Per connection\n\n```typescript\nbuilder.queryFields((t) => ({\n things: t.connection({\n type: SomeThings,\n edgesNullable: {\n items: true,\n list: false,\n },\n nodeNullable: false,\n resolve: (parent, args) => {\n return resolveOffsetConnection({ args }, ({ limit, offset }) => {\n return getThings(offset, limit);\n });\n },\n }),\n}));\n// Or\n\nconst ThingsConnection = builder.connectionObject({\n type: SomeThing,\n name: 'ThingsConnection',\n edgesNullable: {\n items: true,\n list: false,\n },\n nodeNullable: false,\n});\n```\n"
}

@@ -21,2 +21,3 @@ # Relay Plugin

relayOptions: {
// These will become the defaults in the next major version
clientMutationId: 'omit',

@@ -33,3 +34,3 @@ cursorType: 'String',

- `clientMutationId`: `required` (default) | `omit` | `optional`. Determins if clientMutationId
fields are created on connections, and if they are required.
fields are created on `relayMutationFields`, and if they are required.
- `cursorType`: `String` | `ID`. Determines type used for cursor fields. Defaults behavior due to

@@ -127,2 +128,3 @@ legacy reasons is `String` for everything except for connection arguments which use `ID`.

builder.node(NumberThing, {
// defiine an id field
id: {

@@ -132,4 +134,9 @@ resolve: (num) => num.id,

},
// 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',

@@ -144,6 +151,8 @@ fields: (t) => ({

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 `loadOne` and `loadMany` methods are
optional, and `loadMany` will be used if both are present. These methods allow a nodes to be loaded
by id. The relay plugin adds to new query fields `node` and `nodes` which can be used to directly
fetch nodes using global IDs.
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 `laodMany` 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.

@@ -217,3 +226,3 @@ Nodes may also implement an `isTypeOf` method which can be used to resolve the correct type for

builder.queryFields((t) => ({
numbers: t.connection({
things: t.connection({
type: SomeThings,

@@ -247,6 +256,6 @@ resolve: (parent, args) => {

builder.queryFields((t) => ({
numbers: t.connection({
things: t.connection({
type: SomeThings,
resolve: (parent, args) => {
return resolveOffsetConnection({ args }, getAllTheThingsAsArray());
return resolveArrayConnection({ args }, getAllTheThingsAsArray());
},

@@ -353,3 +362,3 @@ }),

{
// connection optionss
// connection options
type: SomeThing,

@@ -435,3 +444,3 @@ name: 'ThingsConnection',

```typescript
import '@pothos/plugin-relay';
import RelayPlugin from '@pothos/plugin-relay';
const builder = new SchemaBuilder({

@@ -518,1 +527,64 @@ plugins: [RelayPlugin],

```
### Changing nullability of edges and nodes
If you want to change the nullability of the `edges` field on a `Connection` or the `node` field on
an `Edge` you can configure this in 2 ways:
#### Globally
```typescript
import RelayPlugin from '@pothos/plugin-relay';
const builder = new SchemaBuilder<{
DefaultEdgesNullability: false;
DefaultNodeNullability: true;
}>({
plugins: [RelayPlugin],
relayOptions: {
edgesFieldOptions: {
nullable: false,
},
nodeFieldOptions: {
nullable: true,
},
},
});
```
The types provided for `DefaultEdgesNullability` and `DefaultNodeNullability` must match the values
provided in the nullable option of `edgesFieldOptions` and `nodeFieldOptions` respectivly. This will
set the default nullabilitty for all connections created by your builder.
Nullablity for `edges` fields defaults to `{ list: false, items: true }` and the nullablity of
`node` fields default to `false`.
#### Per connection
```typescript
builder.queryFields((t) => ({
things: t.connection({
type: SomeThings,
edgesNullable: {
items: true,
list: false,
},
nodeNullable: false,
resolve: (parent, args) => {
return resolveOffsetConnection({ args }, ({ limit, offset }) => {
return getThings(offset, limit);
});
},
}),
}));
// Or
const ThingsConnection = builder.connectionObject({
type: SomeThing,
name: 'ThingsConnection',
edgesNullable: {
items: true,
list: false,
},
nodeNullable: false,
});
```

@@ -64,18 +64,18 @@ /* eslint-disable @typescript-eslint/no-unsafe-return */

hasNextPage: t.exposeBoolean('hasNextPage', {
nullable: false,
...hasNextPageFieldOptions,
nullable: false,
}),
hasPreviousPage: t.exposeBoolean('hasPreviousPage', {
nullable: false,
...hasPreviousPageFieldOptions,
nullable: false,
}),
startCursor: t.expose('startCursor', {
nullable: true,
...startCursorFieldOptions,
type: cursorType,
nullable: true,
}) as never,
endCursor: t.expose('endCursor', {
nullable: true,
...endCursorFieldOptions,
type: cursorType,
nullable: true,
}) as never,

@@ -101,2 +101,3 @@ }),

id: t.globalID({
nullable: false,
resolve: (parent) => {

@@ -113,2 +114,3 @@ throw new Error('id field not implemented');

t.field({
nullable: true,
...this.options.relayOptions.nodeQueryOptions,

@@ -119,3 +121,2 @@ type: ref as InterfaceRef<unknown>,

},
nullable: true,
resolve: async (root, args, context, info) =>

@@ -128,2 +129,6 @@ (await resolveNodes(this, context, info, [String(args.id)]))[0],

t.field({
nullable: {
list: false,
items: true,
},
...this.options.relayOptions.nodesQueryOptions,

@@ -134,13 +139,6 @@ type: [ref],

},
nullable: {
list: false,
items: true,
},
resolve: async (root, args, context, info) =>
(await resolveNodes(
this,
context,
info,
args.ids as string[],
)) as Promise<ObjectParam<SchemaTypes> | null>[],
(await resolveNodes(this, context, info, args.ids as string[])) as Promise<
ObjectParam<SchemaTypes>
>[],
}),

@@ -228,4 +226,4 @@ );

t.globalID<{}, false, Promise<GlobalIDShape<SchemaTypes>>>({
nullable: false,
...options.id,
nullable: false,
args: {},

@@ -330,4 +328,4 @@ resolve: async (parent, args, context, info) => ({

clientMutationId: t.id({
nullable: this.options.relayOptions.clientMutationId === 'optional',
...clientMutationIdFieldOptions,
nullable: this.options.relayOptions.clientMutationId === 'optional',
resolve: (parent, args, context, info) =>

@@ -418,2 +416,3 @@ mutationIdCache(context).get(String(info.path.prev!.key))!,

pageInfo: t.field({
nullable: false,
...pageInfoFieldOptions,

@@ -424,5 +423,5 @@ type: this.pageInfoRef(),

edges: t.field({
nullable: (edgesNullableField ?? edgesNullable) as { list: false; items: true },
...edgesFieldOptions,
type: [edgeRef],
nullable: (edgesNullableField ?? edgesNullable) as { list: false; items: true },
resolve: (parent) => parent.edges,

@@ -438,4 +437,4 @@ }),

node: t.field({
nullable: nodeNullableField ?? nodeNullable,
...nodeFieldOptions,
nullable: nodeNullableField ?? nodeNullable,
type,

@@ -445,2 +444,3 @@ resolve: (parent) => parent.node as never,

cursor: t.expose('cursor', {
nullable: false,
type: cursorType,

@@ -447,0 +447,0 @@ ...cursorFieldOptions,

@@ -42,7 +42,7 @@ import { GraphQLResolveInfo } from 'graphql';

OutputRefShape<GlobalIDShape<Types> | string>,
true,
boolean,
{ id: InputFieldRef<InputShape<Types, 'ID'>> },
Promise<unknown>
>,
'args' | 'nullable' | 'resolve' | 'type'
'args' | 'resolve' | 'type'
>;

@@ -53,15 +53,15 @@ nodesQueryOptions: Omit<

[OutputRefShape<GlobalIDShape<Types> | string>],
true,
FieldNullability<[unknown]>,
{ ids: InputFieldRef<InputShape<Types, 'ID'>[]> },
Promise<unknown>[]
>,
'args' | 'nullable' | 'resolve' | 'type'
'args' | 'resolve' | 'type'
>;
mutationInputArgOptions: Omit<
PothosSchemaTypes.ArgFieldOptions<Types, InputRef<{}>, true>,
'fields' | 'required' | 'type'
PothosSchemaTypes.ArgFieldOptions<Types, InputRef<{}>, boolean>,
'fields' | 'type'
>;
clientMutationIdInputOptions: Omit<
PothosSchemaTypes.InputObjectFieldOptions<Types, 'ID', true>,
'required' | 'type'
PothosSchemaTypes.InputObjectFieldOptions<Types, 'ID', boolean>,
'type'
>;

@@ -73,7 +73,7 @@ clientMutationIdFieldOptions: Omit<

'ID',
false,
boolean,
{},
Types['Scalars']['ID']['Output']
>,
'args' | 'nullable' | 'resolve' | 'type'
'args' | 'resolve' | 'type'
>;

@@ -90,3 +90,3 @@ cursorFieldOptions: Normalize<

>,
'args' | 'nullable' | 'resolve' | 'type'
'args' | 'resolve' | 'type'
> & {

@@ -101,3 +101,3 @@ type?: 'ID' | 'String';

ObjectRef<{}>,
false,
Types['DefaultNodeNullability'],
{},

@@ -111,4 +111,11 @@ GlobalIDShape<Types> | string

edgesFieldOptions: Omit<
PothosSchemaTypes.ObjectFieldOptions<Types, {}, [ObjectRef<{}>], false, {}, unknown[]>,
'args' | 'nullable' | 'resolve' | 'type'
PothosSchemaTypes.ObjectFieldOptions<
Types,
{},
[ObjectRef<{}>],
Types['DefaultEdgesNullability'],
{},
unknown[]
>,
'args' | 'resolve' | 'nullable' | 'type'
> & {

@@ -122,15 +129,15 @@ nullable?: Types['DefaultEdgesNullability'];

OutputRef<PageInfoShape>,
false,
boolean,
{},
PageInfoShape
>,
'args' | 'nullable' | 'resolve' | 'type'
'args' | 'resolve' | 'type'
>;
hasNextPageFieldOptions: Omit<
PothosSchemaTypes.ObjectFieldOptions<Types, PageInfoShape, 'Boolean', false, {}, boolean>,
'args' | 'nullable' | 'resolve' | 'type'
PothosSchemaTypes.ObjectFieldOptions<Types, PageInfoShape, 'Boolean', boolean, {}, boolean>,
'args' | 'resolve' | 'type'
>;
hasPreviousPageFieldOptions: Omit<
PothosSchemaTypes.ObjectFieldOptions<Types, PageInfoShape, 'Boolean', false, {}, boolean>,
'args' | 'nullable' | 'resolve' | 'type'
PothosSchemaTypes.ObjectFieldOptions<Types, PageInfoShape, 'Boolean', boolean, {}, boolean>,
'args' | 'resolve' | 'type'
>;

@@ -142,7 +149,7 @@ startCursorFieldOptions: Omit<

'ID' | 'String',
false,
boolean,
{},
string | null
>,
'args' | 'nullable' | 'resolve' | 'type'
'args' | 'resolve' | 'type'
>;

@@ -154,22 +161,22 @@ endCursorFieldOptions: Omit<

'ID' | 'String',
false,
boolean,
{},
string | null
>,
'args' | 'nullable' | 'resolve' | 'type'
'args' | 'resolve' | 'type'
>;
beforeArgOptions: Omit<
PothosSchemaTypes.InputObjectFieldOptions<Types, 'ID' | 'String', false>,
PothosSchemaTypes.InputObjectFieldOptions<Types, 'ID' | 'String', boolean>,
'required' | 'type'
>;
afterArgOptions: Omit<
PothosSchemaTypes.InputObjectFieldOptions<Types, 'ID' | 'String', false>,
PothosSchemaTypes.InputObjectFieldOptions<Types, 'ID' | 'String', boolean>,
'required' | 'type'
>;
firstArgOptions: Omit<
PothosSchemaTypes.InputObjectFieldOptions<Types, 'Int', false>,
PothosSchemaTypes.InputObjectFieldOptions<Types, 'Int', boolean>,
'required' | 'type'
>;
lastArgOptions: Omit<
PothosSchemaTypes.InputObjectFieldOptions<Types, 'Int', false>,
PothosSchemaTypes.InputObjectFieldOptions<Types, 'Int', boolean>,
'required' | 'type'

@@ -176,0 +183,0 @@ >;

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

SocketSocket SOC 2 Logo

Product

  • Package Alerts
  • Integrations
  • Docs
  • Pricing
  • FAQ
  • Roadmap
  • Changelog

Packages

npm

Stay in touch

Get open source security insights delivered straight into your inbox.


  • Terms
  • Privacy
  • Security

Made with ⚡️ by Socket Inc