Comments (2)
You’ll be pleased to hear V5 already uses this (or similar) technique. I’m hesitant to add a feature like this to v4 that would be soon replaced by a V5 alternative which cannot be back-ported due to a dramatic shift in technology. I think it may be best for you to maintain a fork of the relevant package/plugin (or even a patch-package patch) for now.
from graphile-engine.
Awesome! Looking forward to V5.
For anyone reading this before V5, this is what we did:
// CustomNodePlugin.ts
import type { Build, DataForType } from 'graphile-build'
import type {
GraphQLInterfaceType,
GraphQLResolveInfo,
GraphQLType,
} from 'graphql'
import type { ResolveTree } from 'graphql-parse-resolve-info'
import type { Plugin, SchemaBuilder } from 'postgraphile'
export type Transform = (input: string) => string
const defaultEncode: Transform = (str) =>
Buffer.from(String(str)).toString('base64')
const defaultDecode: Transform = (str) =>
Buffer.from(String(str), 'base64').toString('utf8')
export type NodeFetcher = (
data: any,
identifiers: any[],
context: any,
parsedResolveInfoFragment: ResolveTree,
type: GraphQLType,
resolveData: DataForType,
resolveInfo: GraphQLResolveInfo,
) => {}
export type BuildExtensionNode = {
nodeIdFieldName: string
$$nodeType: any
nodeFetcherByTypeName: Record<string, NodeFetcher>
getNodeIdForTypeAndIdentifiers(
Type: GraphQLType,
...identifiers: any[]
): string
getTypeAndIdentifiersFromNodeId(nodeId: string): {
Type: GraphQLType
identifiers: any[]
}
addNodeFetcherForTypeName(typeName: string, fetcher: NodeFetcher): void
getNodeAlias(typeName: string): string
getNodeType(alias: string): GraphQLType
setNodeAlias(typeName: string, alias: string): void
}
const CustomNodePlugin: Plugin = (
builder: SchemaBuilder,
{ nodeIdFieldName: inNodeIdFieldName, nodeIdGenerator }: any,
): void => {
const encode = nodeIdGenerator?.encode ?? defaultEncode
const decode = nodeIdGenerator?.decode ?? defaultDecode
const nodeIdFieldName: string = inNodeIdFieldName
? String(inNodeIdFieldName)
: 'id'
builder.hook(
'build',
(build: Build): Build & BuildExtensionNode => {
const nodeFetcherByTypeName: Record<string, any> = {}
const nodeAliasByTypeName: Record<string, any> = {}
const nodeTypeNameByAlias: Record<string, any> = {}
return build.extend(
build,
{
nodeIdFieldName,
$$nodeType: Symbol('nodeType'),
nodeFetcherByTypeName,
getNodeIdForTypeAndIdentifiers(Type: any, ...identifiers: any[]) {
return encode(
JSON.stringify([this.getNodeAlias(Type), ...identifiers]),
)
},
getTypeAndIdentifiersFromNodeId(nodeId: any) {
const [alias, ...identifiers] = JSON.parse(decode(nodeId))
return {
Type: this.getNodeType(alias),
identifiers,
}
},
addNodeFetcherForTypeName(typeName: any, fetcher: any) {
if (nodeFetcherByTypeName[typeName]) {
throw new Error("There's already a fetcher for this type")
}
if (!fetcher) {
throw new Error('No fetcher specified')
}
nodeFetcherByTypeName[typeName] = fetcher
},
getNodeAlias(typeName: any) {
return nodeAliasByTypeName[typeName] || typeName
},
getNodeType(alias: any) {
return this.getTypeByName(nodeTypeNameByAlias[alias] || alias)
},
setNodeAlias(typeName: any, alias: any) {
if (
nodeTypeNameByAlias[alias] &&
nodeTypeNameByAlias[alias] !== typeName
) {
// eslint-disable-next-line no-console
console.warn(
`SERIOUS WARNING: two GraphQL types (${typeName} and ${nodeTypeNameByAlias[alias]}) are trying to use the same node alias '${alias}' which may mean that the Relay Global Object Identification identifiers in your schema may not be unique. To solve this, you should skip the PgNodeAliasPostGraphile plugin, but note this will change all your existing Node IDs. For alternative solutions, get in touch via GitHub or Discord`,
)
}
nodeAliasByTypeName[typeName] = alias
nodeTypeNameByAlias[alias] = typeName
},
},
"Adding 'Node' interface support to the Build",
)
},
['Node'],
)
builder.hook(
'init',
function defineNodeInterfaceType(_, build) {
const {
$$isQuery,
$$nodeType,
getTypeByName,
newWithHooks,
graphql: {
GraphQLNonNull,
GraphQLID,
GraphQLInterfaceType,
getNullableType,
},
inflection,
} = build
let Query: any
newWithHooks(
GraphQLInterfaceType,
{
name: inflection.builtin('Node'),
description: build.wrapDescription(
'An object with a globally unique `ID`.',
'type',
),
resolveType: (value: any) => {
if (value === $$isQuery) {
if (!Query) {
Query = getTypeByName(inflection.builtin('Query'))
}
return Query
} else if (value[$$nodeType]) {
return getNullableType(value[$$nodeType])
}
},
fields: {
[nodeIdFieldName]: {
description: build.wrapDescription(
'A globally unique identifier. Can be used in various places throughout the system to identify this single value.',
'field',
),
type: new GraphQLNonNull(GraphQLID),
},
},
},
{
__origin:
"graphile-build built-in (NodePlugin); you can omit this plugin if you like, but you'll lose compatibility with Relay",
},
)
return _
},
['Node'],
)
builder.hook(
'GraphQLObjectType:interfaces',
function addNodeIdToQuery(
interfaces: GraphQLInterfaceType[],
build,
context,
) {
const { getTypeByName, inflection } = build
const {
scope: { isRootQuery },
} = context
if (!isRootQuery) {
return interfaces
}
const Type = getTypeByName(inflection.builtin('Node'))
if (Type) {
return [...interfaces, Type]
} else {
return interfaces
}
},
['Node'],
)
builder.hook(
'GraphQLObjectType:fields',
(fields, build, context) => {
const {
scope: { isRootQuery },
fieldWithHooks,
} = context
if (!isRootQuery) {
return fields
}
const {
getTypeByName,
extend,
graphql: { GraphQLNonNull, GraphQLID },
inflection,
resolveNode,
} = build
return extend(
fields,
{
[nodeIdFieldName]: {
description: build.wrapDescription(
'The root query type must be a `Node` to work well with Relay 1 mutations. This just resolves to `query`.',
'field',
),
type: new GraphQLNonNull(GraphQLID),
resolve() {
return 'query'
},
},
node: fieldWithHooks(
'node',
({ getDataFromParsedResolveInfoFragment }: any): any => ({
description: build.wrapDescription(
'Fetches an object given its globally unique `ID`.',
'field',
),
type: getTypeByName(inflection.builtin('Node')),
args: {
[nodeIdFieldName]: {
description: build.wrapDescription(
'The globally unique `ID`.',
'arg',
),
type: new GraphQLNonNull(GraphQLID),
},
},
resolve(
data: any,
args: any,
context2: any,
resolveInfo: any,
): any {
const nodeId = args[nodeIdFieldName]
return resolveNode(
nodeId,
build,
{ getDataFromParsedResolveInfoFragment },
data,
context2,
resolveInfo,
)
},
}),
{
isRootNodeField: true,
},
),
},
`Adding Relay Global Object Identification support to the root Query via 'node' and '${nodeIdFieldName}' fields`,
)
},
['Node'],
)
}
export default CustomNodePlugin
from graphile-engine.
Related Issues (20)
- InitObject is typed wrong HOT 4
- [graphql-parse-resolve-info] Fix types or example HOT 3
- Add depth traversal to `simplifyParsedResolveInfoFragmentWithType` HOT 2
- No Way to Access Request From Resolvers? HOT 1
- [graphile-build]: Types for Build Don't Match Source HOT 4
- graphql-parse-resolve-info scalar fields HOT 2
- Pagination cursors not working for tables with at least one bigint component HOT 6
- Modify column SQL select HOT 1
- graphile/subscriptions-lds does not trigger on DELETE if subscription contains only edges
- Project depends on vulnerable version of jsonwebtoken HOT 2
- graphql-parse-resolve-info - What to do to get the same output like graphql-fields? HOT 5
- Don't silently swallow exceptions in resolveNode HOT 2
- alias a column at mutation time only? HOT 2
- Type with array property triggers error HOT 10
- Error using PG client: "We only support PG clients from a PG pool..." HOT 4
- pg-pubsub call to removeListener looks like it will not work HOT 3
- Provide a type guard to distinguish between ResolveTree and FieldsByTypeName HOT 2
- Multiple services resulting in duplicate codecs for simple types HOT 1
- Defining two types with the same schema should be allowed HOT 1
Recommend Projects
-
React
A declarative, efficient, and flexible JavaScript library for building user interfaces.
-
Vue.js
🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
-
Typescript
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
-
TensorFlow
An Open Source Machine Learning Framework for Everyone
-
Django
The Web framework for perfectionists with deadlines.
-
Laravel
A PHP framework for web artisans
-
D3
Bring data to life with SVG, Canvas and HTML. 📊📈🎉
-
Recommend Topics
-
javascript
JavaScript (JS) is a lightweight interpreted programming language with first-class functions.
-
web
Some thing interesting about web. New door for the world.
-
server
A server is a program made to process requests and deliver data to clients.
-
Machine learning
Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.
-
Visualization
Some thing interesting about visualization, use data art
-
Game
Some thing interesting about game, make everyone happy.
Recommend Org
-
Facebook
We are working to build community through open source technology. NB: members must have two-factor auth.
-
Microsoft
Open source projects and samples from Microsoft.
-
Google
Google ❤️ Open Source for everyone.
-
Alibaba
Alibaba Open Source for everyone
-
D3
Data-Driven Documents codes.
-
Tencent
China tencent open source team.
from graphile-engine.