New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
createRequest
with transforms API
#724
Changes from 6 commits
f9c46be
33dcd04
db3a233
b3a97c6
34d4eb5
5552a3e
02db0a4
251d0dc
8c5e3c8
a726453
a98d8cc
6b28177
242cde0
a8704ac
fade3fb
4be53ed
5bb040b
6592810
11697ae
e657fae
ce7a18d
eeb1a84
bbc0265
30b9119
188442d
88407e4
1b8cafd
8fd2a46
9e92ba6
5f46b3e
779100b
fed0f01
948355e
a61afae
ffd5f4c
a8e5038
2a8e82c
536805b
e08cc10
5607de4
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,134 +1,155 @@ | ||
import { | ||
ArgumentNode, | ||
DocumentNode, | ||
FieldNode, | ||
FragmentDefinitionNode, | ||
Kind, | ||
OperationDefinitionNode, | ||
SelectionSetNode, | ||
SelectionNode, | ||
subscribe, | ||
execute, | ||
validate, | ||
VariableDefinitionNode, | ||
GraphQLSchema, | ||
GraphQLResolveInfo | ||
} from 'graphql'; | ||
import { Operation, Request, IDelegateToSchemaOptions } from '../Interfaces'; | ||
import { | ||
applyRequestTransforms, | ||
applyResultTransforms, | ||
} from '../transforms/transforms'; | ||
import { FetcherOperation } from './makeRemoteExecutableSchema'; | ||
import { Request, Transform, IDelegateToSchemaOptions } from '../Interfaces'; | ||
import { applyRequestTransforms, applyResultTransforms } from '../transforms/transforms'; | ||
import AddArgumentsAsVariables from '../transforms/AddArgumentsAsVariables'; | ||
import FilterToSchema from '../transforms/FilterToSchema'; | ||
import AddTypenameToAbstract from '../transforms/AddTypenameToAbstract'; | ||
import CheckResultAndHandleErrors from '../transforms/CheckResultAndHandleErrors'; | ||
|
||
export default async function delegateToSchema( | ||
options: IDelegateToSchemaOptions, | ||
): Promise<any> { | ||
const { info, args = {} } = options; | ||
const rawDocument: DocumentNode = createDocument( | ||
options.fieldName, | ||
options.operation, | ||
info.fieldNodes, | ||
Object.keys(info.fragments).map( | ||
fragmentName => info.fragments[fragmentName], | ||
), | ||
info.operation.variableDefinitions, | ||
export type OperationRootDefinition = { | ||
fieldName: string, | ||
alias?: string, | ||
args?: { [key: string]: any }, | ||
info: GraphQLResolveInfo | ||
}; | ||
|
||
export function createOperation( | ||
targetSchema: GraphQLSchema, | ||
targetOperation: 'query' | 'mutation' | 'subscription', | ||
rootDefs: Array<OperationRootDefinition>, | ||
graphqlContext: { [key: string]: any }, | ||
documentInfo: GraphQLResolveInfo, | ||
transforms?: Array<Transform>, | ||
): FetcherOperation { | ||
const roots = rootDefs.map(def => ({ ...def, key: def.alias || def.fieldName })); | ||
|
||
const selections: Array<SelectionNode> = roots.reduce((newSelections, { key, fieldName, info, alias, args }) => { | ||
const rootSelections = info.fieldNodes.map((selection: FieldNode) => { | ||
if (selection.kind === Kind.FIELD) { | ||
const rootField: FieldNode = { | ||
...selection, | ||
name: { | ||
kind: Kind.NAME, | ||
value: fieldName, | ||
}, | ||
alias: alias | ||
? { | ||
kind: Kind.NAME, | ||
value: alias | ||
} | ||
: null | ||
}; | ||
return rootField; | ||
} | ||
return selection; | ||
}); | ||
|
||
return newSelections.concat(rootSelections); | ||
}, []); | ||
|
||
const selectionSet: SelectionSetNode = { | ||
kind: Kind.SELECTION_SET, | ||
selections, | ||
}; | ||
|
||
const operationDefinition: OperationDefinitionNode = { | ||
kind: Kind.OPERATION_DEFINITION, | ||
operation: targetOperation, | ||
variableDefinitions: documentInfo.operation.variableDefinitions, | ||
selectionSet, | ||
}; | ||
|
||
const fragments = Object.keys(documentInfo.fragments).map( | ||
fragmentName => documentInfo.fragments[fragmentName], | ||
); | ||
|
||
const document = { | ||
kind: Kind.DOCUMENT, | ||
definitions: [operationDefinition, ...fragments], | ||
}; | ||
|
||
const rawRequest: Request = { | ||
document: rawDocument, | ||
variables: info.variableValues as Record<string, any>, | ||
document, | ||
variables: documentInfo.variableValues as Record<string, any>, | ||
}; | ||
|
||
const transforms = [ | ||
...(options.transforms || []), | ||
AddArgumentsAsVariables(options.schema, args), | ||
FilterToSchema(options.schema), | ||
AddTypenameToAbstract(options.schema), | ||
CheckResultAndHandleErrors(info, options.fieldName), | ||
transforms = [ | ||
...(transforms || []), | ||
AddArgumentsAsVariables(targetSchema, roots), | ||
FilterToSchema(targetSchema), | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I'm pretty damn proud this still works without any problem :D |
||
AddTypenameToAbstract(targetSchema) | ||
]; | ||
|
||
const processedRequest = applyRequestTransforms(rawRequest, transforms); | ||
const { document: query, variables } = applyRequestTransforms(rawRequest, transforms); | ||
|
||
return { | ||
query, | ||
variables, | ||
context: graphqlContext, | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Should we return There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Or should we remove context all together, and just have this function return There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Just document and variables I think. |
||
operationName: documentInfo.operation && documentInfo.operation.name && documentInfo.operation.name.value | ||
}; | ||
} | ||
|
||
const errors = validate(options.schema, processedRequest.document); | ||
export default async function delegateToSchema( | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Pet peeve - could you move default export on top? |
||
options: IDelegateToSchemaOptions, | ||
): Promise<any> { | ||
const processedRequest = createOperation( | ||
options.schema, | ||
options.operation, | ||
[ | ||
{ | ||
fieldName: options.fieldName, | ||
args: options.args || {}, | ||
info: options.info | ||
} | ||
], | ||
options.context, | ||
options.info, | ||
options.transforms | ||
); | ||
|
||
const errors = validate(options.schema, processedRequest.query); | ||
if (errors.length > 0) { | ||
throw errors; | ||
} | ||
|
||
if (options.operation === 'query' || | ||
options.operation === 'mutation') { | ||
return applyResultTransforms(await execute( | ||
if (options.operation === 'query' || options.operation === 'mutation') { | ||
const rawResult = await execute( | ||
options.schema, | ||
processedRequest.document, | ||
info.rootValue, | ||
processedRequest.query, | ||
options.info.rootValue, | ||
options.context, | ||
processedRequest.variables, | ||
), transforms); | ||
); | ||
|
||
const result = applyResultTransforms(rawResult, [ | ||
...(options.transforms || []), | ||
CheckResultAndHandleErrors(options.info, options.fieldName), | ||
]); | ||
|
||
return result; | ||
} | ||
|
||
if (options.operation === 'subscription') { | ||
// apply result processing ??? | ||
return subscribe( | ||
options.schema, | ||
processedRequest.document, | ||
info.rootValue, | ||
processedRequest.query, | ||
options.info.rootValue, | ||
options.context, | ||
processedRequest.variables, | ||
); | ||
} | ||
} | ||
|
||
function createDocument( | ||
targetField: string, | ||
targetOperation: Operation, | ||
originalSelections: Array<SelectionNode>, | ||
fragments: Array<FragmentDefinitionNode>, | ||
variables: Array<VariableDefinitionNode>, | ||
): DocumentNode { | ||
let selections: Array<SelectionNode> = []; | ||
let args: Array<ArgumentNode> = []; | ||
|
||
originalSelections.forEach((field: FieldNode) => { | ||
const fieldSelections = field.selectionSet | ||
? field.selectionSet.selections | ||
: []; | ||
selections = selections.concat(fieldSelections); | ||
args = args.concat(field.arguments || []); | ||
}); | ||
|
||
let selectionSet = null; | ||
if (selections.length > 0) { | ||
selectionSet = { | ||
kind: Kind.SELECTION_SET, | ||
selections: selections, | ||
}; | ||
} | ||
|
||
const rootField: FieldNode = { | ||
kind: Kind.FIELD, | ||
alias: null, | ||
arguments: args, | ||
selectionSet, | ||
name: { | ||
kind: Kind.NAME, | ||
value: targetField, | ||
}, | ||
}; | ||
const rootSelectionSet: SelectionSetNode = { | ||
kind: Kind.SELECTION_SET, | ||
selections: [rootField], | ||
}; | ||
|
||
const operationDefinition: OperationDefinitionNode = { | ||
kind: Kind.OPERATION_DEFINITION, | ||
operation: targetOperation, | ||
variableDefinitions: variables, | ||
selectionSet: rootSelectionSet, | ||
}; | ||
|
||
return { | ||
kind: Kind.DOCUMENT, | ||
definitions: [operationDefinition, ...fragments], | ||
}; | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I left this for now, but let me know if you want me to change this.
documentInfo
was a prop i though users could use to define properties that are global to that operation, and do not affect specific root fields. Like fragments, for example.