From 271932432b2846db5dac2effcf7ab756c56e8a65 Mon Sep 17 00:00:00 2001 From: Tianyu Yao Date: Mon, 6 Jul 2020 16:19:38 -0700 Subject: [PATCH] @appendEdge and @prependEdge handlefield generation Reviewed By: josephsavona Differential Revision: D22346924 fbshipit-source-id: 1619adabede7308c4522946260d311825e3c4929 --- .../codegen/NormalizationCodeGenerator.js | 9 + .../compileRelayArtifacts-test.js.snap | 178 ++++++++++++++++++ .../compileRelayArtifacts/append-edge.graphql | 13 ++ packages/relay-compiler/core/IR.js | 1 + packages/relay-compiler/core/IRPrinter.js | 6 +- .../DeclarativeConnectionMutationTransform.js | 75 +++++++- ...veConnectionMutationTransform-test.js.snap | 60 ++++++ .../append-edge-unspported.invalid.graphql | 11 ++ .../append-edge.graphql | 13 ++ .../relay-runtime/util/NormalizationNode.js | 1 + 10 files changed, 363 insertions(+), 4 deletions(-) create mode 100644 packages/relay-compiler/codegen/__tests__/fixtures/compileRelayArtifacts/append-edge.graphql create mode 100644 packages/relay-compiler/transforms/__tests__/fixtures/declarative-connection-mutation-transform/append-edge-unspported.invalid.graphql create mode 100644 packages/relay-compiler/transforms/__tests__/fixtures/declarative-connection-mutation-transform/append-edge.graphql diff --git a/packages/relay-compiler/codegen/NormalizationCodeGenerator.js b/packages/relay-compiler/codegen/NormalizationCodeGenerator.js index fe584caa73e65..bed15511b15cc 100644 --- a/packages/relay-compiler/codegen/NormalizationCodeGenerator.js +++ b/packages/relay-compiler/codegen/NormalizationCodeGenerator.js @@ -305,6 +305,15 @@ function generateLinkedField( }, }; } + if (handle.handleArgs != null) { + const handleArgs = generateArgs(handle.handleArgs); + if (handleArgs != null) { + handleNode = { + ...handleNode, + handleArgs, + }; + } + } return handleNode; })) || []; diff --git a/packages/relay-compiler/codegen/__tests__/__snapshots__/compileRelayArtifacts-test.js.snap b/packages/relay-compiler/codegen/__tests__/__snapshots__/compileRelayArtifacts-test.js.snap index cea9d2ed7102d..76d11e7cd3035 100644 --- a/packages/relay-compiler/codegen/__tests__/__snapshots__/compileRelayArtifacts-test.js.snap +++ b/packages/relay-compiler/codegen/__tests__/__snapshots__/compileRelayArtifacts-test.js.snap @@ -1907,6 +1907,184 @@ mutation ChangeNameMutation( `; +exports[`compileRelayArtifacts matches expected output: append-edge.graphql 1`] = ` +~~~~~~~~~~ INPUT ~~~~~~~~~~ +mutation CommentCreateMutation( + $connections: [String!]! + $input: CommentCreateInput +) { + commentCreate(input: $input) { + feedbackCommentEdge @appendEdge(connections: $connections) { + cursor + node { + id + } + } + } +} + +~~~~~~~~~~ OUTPUT ~~~~~~~~~~ +Request { + fragment: Fragment { + argumentDefinitions: [ + LocalArgument { + defaultValue: null, + name: "connections", + }, + LocalArgument { + defaultValue: null, + name: "input", + }, + ], + metadata: null, + name: "CommentCreateMutation", + selections: [ + LinkedField { + alias: null, + args: [ + Variable { + name: "input", + variableName: "input", + }, + ], + concreteType: "CommentCreateResponsePayload", + name: "commentCreate", + plural: false, + selections: [ + LinkedField { + alias: null, + args: null, + concreteType: "CommentsEdge", + name: "feedbackCommentEdge", + plural: false, + selections: [ + ScalarField { + alias: null, + args: null, + name: "cursor", + storageKey: null, + }, + LinkedField { + alias: null, + args: null, + concreteType: "Comment", + name: "node", + plural: false, + selections: [ + ScalarField { + alias: null, + args: null, + name: "id", + storageKey: null, + }, + ], + storageKey: null, + }, + ], + storageKey: null, + }, + ], + storageKey: null, + }, + ], + type: "Mutation", + abstractKey: null, + }, + operation: Operation { + argumentDefinitions: [ + LocalArgument { + defaultValue: null, + name: "connections", + }, + LocalArgument { + defaultValue: null, + name: "input", + }, + ], + name: "CommentCreateMutation", + selections: [ + LinkedField { + alias: null, + args: [ + Variable { + name: "input", + variableName: "input", + }, + ], + concreteType: "CommentCreateResponsePayload", + name: "commentCreate", + plural: false, + selections: [ + LinkedField { + alias: null, + args: null, + concreteType: "CommentsEdge", + name: "feedbackCommentEdge", + plural: false, + selections: [ + ScalarField { + alias: null, + args: null, + name: "cursor", + storageKey: null, + }, + LinkedField { + alias: null, + args: null, + concreteType: "Comment", + name: "node", + plural: false, + selections: [ + ScalarField { + alias: null, + args: null, + name: "id", + storageKey: null, + }, + ], + storageKey: null, + }, + ], + storageKey: null, + }, + LinkedHandle { + alias: null, + args: null, + filters: null, + handle: "appendEdge", + key: "", + name: "feedbackCommentEdge", + handleArgs: [ + Variable { + name: "connections", + variableName: "connections", + }, + ], + }, + ], + storageKey: null, + }, + ], + }, +} + +QUERY: + +mutation CommentCreateMutation( + $input: CommentCreateInput +) { + commentCreate(input: $input) { + feedbackCommentEdge { + cursor + node { + id + } + } + } +} + +`; + exports[`compileRelayArtifacts matches expected output: client-conditions.graphql 1`] = ` ~~~~~~~~~~ INPUT ~~~~~~~~~~ fragment Foo_user on User { diff --git a/packages/relay-compiler/codegen/__tests__/fixtures/compileRelayArtifacts/append-edge.graphql b/packages/relay-compiler/codegen/__tests__/fixtures/compileRelayArtifacts/append-edge.graphql new file mode 100644 index 0000000000000..5df51f2d46c7e --- /dev/null +++ b/packages/relay-compiler/codegen/__tests__/fixtures/compileRelayArtifacts/append-edge.graphql @@ -0,0 +1,13 @@ +mutation CommentCreateMutation( + $connections: [String!]! + $input: CommentCreateInput +) { + commentCreate(input: $input) { + feedbackCommentEdge @appendEdge(connections: $connections) { + cursor + node { + id + } + } + } +} diff --git a/packages/relay-compiler/core/IR.js b/packages/relay-compiler/core/IR.js index 1902a1908bf4c..01121c9712d04 100644 --- a/packages/relay-compiler/core/IR.js +++ b/packages/relay-compiler/core/IR.js @@ -181,6 +181,7 @@ export type Handle = {| // T45504512: new connection model +dynamicKey: Variable | null, +filters: ?$ReadOnlyArray, + +handleArgs?: $ReadOnlyArray, |}; export type ClientExtension = {| diff --git a/packages/relay-compiler/core/IRPrinter.js b/packages/relay-compiler/core/IRPrinter.js index 4dcb661a2443e..be960c7d81ff8 100644 --- a/packages/relay-compiler/core/IRPrinter.js +++ b/packages/relay-compiler/core/IRPrinter.js @@ -322,7 +322,11 @@ function printHandles(schema: Schema, field: Field): string { handle.filters == null ? '' : `, filters: ${JSON.stringify(Array.from(handle.filters).sort())}`; - return `@__clientField(handle: "${handle.name}"${key}${filters})`; + const handleArgs = + handle.handleArgs == null + ? '' + : `, handleArgs: ${printArguments(schema, handle.handleArgs)}`; + return `@__clientField(handle: "${handle.name}"${key}${filters}${handleArgs})`; }); return printed.length ? ' ' + printed.join(' ') : ''; } diff --git a/packages/relay-compiler/transforms/DeclarativeConnectionMutationTransform.js b/packages/relay-compiler/transforms/DeclarativeConnectionMutationTransform.js index f417243b18684..eb47d4e1979a3 100644 --- a/packages/relay-compiler/transforms/DeclarativeConnectionMutationTransform.js +++ b/packages/relay-compiler/transforms/DeclarativeConnectionMutationTransform.js @@ -15,14 +15,25 @@ const IRTransformer = require('../core/IRTransformer'); const {createUserError} = require('../core/CompilerError'); +const {ConnectionInterface} = require('relay-runtime'); const DELETE_RECORD = 'deleteRecord'; +const APPEND_EDGE = 'appendEdge'; +const PREPEND_EDGE = 'prependEdge'; +const LINKED_FIELD_DIRECTIVES = [APPEND_EDGE, PREPEND_EDGE]; + const SCHEMA_EXTENSION = ` directive @${DELETE_RECORD} on FIELD + directive @${APPEND_EDGE}( + connections: [String!]! + ) on FIELD + directive @${PREPEND_EDGE}( + connections: [String!]! + ) on FIELD `; import type CompilerContext from '../core/CompilerContext'; -import type {ScalarField, Root, Handle, LinkedField} from '../core/IR'; +import type {ScalarField, LinkedField, Root, Handle} from '../core/IR'; function transform(context: CompilerContext): CompilerContext { return IRTransformer.transform(context, { @@ -46,6 +57,15 @@ function visitRoot(root: Root): Root { } function visitScalarField(field: ScalarField): ScalarField { + const linkedFieldDirective = field.directives.find( + directive => LINKED_FIELD_DIRECTIVES.indexOf(directive.name) > -1, + ); + if (linkedFieldDirective != null) { + throw createUserError( + `Invalid use of @${linkedFieldDirective.name} on scalar field '${field.name}'`, + [linkedFieldDirective.loc], + ); + } const deleteDirective = field.directives.find( directive => directive.name === DELETE_RECORD, ); @@ -83,11 +103,60 @@ function visitLinkedField(field: LinkedField): LinkedField { ); if (deleteDirective != null) { throw createUserError( - `Invalid use of @${deleteDirective.name} on scalar field '${transformedField.name}'`, + `Invalid use of @${deleteDirective.name} on scalar field '${transformedField.name}'.`, [deleteDirective.loc], ); } - return transformedField; + const edgeDirective = transformedField.directives.find( + directive => LINKED_FIELD_DIRECTIVES.indexOf(directive.name) > -1, + ); + if (edgeDirective == null) { + return transformedField; + } + const connectionsArg = edgeDirective.args.find( + arg => arg.name === 'connections', + ); + if (connectionsArg == null) { + throw createUserError( + `Expected the 'connections' argument to be defined on @${edgeDirective.name}.`, + [edgeDirective.loc], + ); + } + const schema = this.getContext().getSchema(); + const fields = schema.getFields(transformedField.type); + let cursorFieldID; + let nodeFieldID; + for (const fieldID of fields) { + const fieldName = schema.getFieldName(fieldID); + if (fieldName === ConnectionInterface.get().CURSOR) { + cursorFieldID = fieldID; + } else if (fieldName === ConnectionInterface.get().NODE) { + nodeFieldID = fieldID; + } + } + // Edge + if (cursorFieldID != null && nodeFieldID != null) { + const handle: Handle = { + name: edgeDirective.name, + key: '', + dynamicKey: null, + filters: null, + handleArgs: [connectionsArg], + }; + return { + ...transformedField, + directives: transformedField.directives.filter( + directive => directive !== edgeDirective, + ), + handles: transformedField.handles + ? [...transformedField.handles, handle] + : [handle], + }; + } + throw createUserError( + `Unsupported use of @${edgeDirective.name} on field '${transformedField.name}', expected an edge field (a field with 'cursor' and 'node' selection).`, + [edgeDirective.loc], + ); } module.exports = { diff --git a/packages/relay-compiler/transforms/__tests__/__snapshots__/DeclarativeConnectionMutationTransform-test.js.snap b/packages/relay-compiler/transforms/__tests__/__snapshots__/DeclarativeConnectionMutationTransform-test.js.snap index ce06d949f0671..78a6f9a6d94e0 100644 --- a/packages/relay-compiler/transforms/__tests__/__snapshots__/DeclarativeConnectionMutationTransform-test.js.snap +++ b/packages/relay-compiler/transforms/__tests__/__snapshots__/DeclarativeConnectionMutationTransform-test.js.snap @@ -1,5 +1,65 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP +exports[`matches expected output: append-edge.graphql 1`] = ` +~~~~~~~~~~ INPUT ~~~~~~~~~~ +mutation CommentCreateMutation( + $connections: [String!]! + $input: CommentCreateInput +) { + commentCreate(input: $input) { + feedbackCommentEdge @appendEdge(connections: $connections) { + cursor + node { + id + } + } + } +} + +~~~~~~~~~~ OUTPUT ~~~~~~~~~~ +mutation CommentCreateMutation( + $connections: [String!]! + $input: CommentCreateInput +) { + commentCreate(input: $input) { + feedbackCommentEdge @__clientField(handle: "appendEdge", handleArgs: (connections: $connections)) { + cursor + node { + id + } + } + } +} + +`; + +exports[`matches expected output: append-edge-unspported.invalid.graphql 1`] = ` +~~~~~~~~~~ INPUT ~~~~~~~~~~ +# expected-to-throw +mutation CommentCreateMutation( + $connections: [String!]! + $input: CommentCreateInput +) { + commentCreate(input: $input) { + viewer @appendEdge(connections: $connections) { + __typename + } + } +} + +~~~~~~~~~~ OUTPUT ~~~~~~~~~~ +THROWN EXCEPTION: + +Unsupported use of @appendEdge on field 'viewer', expected an edge field (a field with 'cursor' and 'node' selection). + +Source: GraphQL request (7:12) +6: commentCreate(input: $input) { +7: viewer @appendEdge(connections: $connections) { + ^ +8: __typename + +`; + exports[`matches expected output: delete-from-store.graphql 1`] = ` ~~~~~~~~~~ INPUT ~~~~~~~~~~ mutation CommentDeleteMutation($input: CommentDeleteInput) { diff --git a/packages/relay-compiler/transforms/__tests__/fixtures/declarative-connection-mutation-transform/append-edge-unspported.invalid.graphql b/packages/relay-compiler/transforms/__tests__/fixtures/declarative-connection-mutation-transform/append-edge-unspported.invalid.graphql new file mode 100644 index 0000000000000..e645fde4766bd --- /dev/null +++ b/packages/relay-compiler/transforms/__tests__/fixtures/declarative-connection-mutation-transform/append-edge-unspported.invalid.graphql @@ -0,0 +1,11 @@ +# expected-to-throw +mutation CommentCreateMutation( + $connections: [String!]! + $input: CommentCreateInput +) { + commentCreate(input: $input) { + viewer @appendEdge(connections: $connections) { + __typename + } + } +} diff --git a/packages/relay-compiler/transforms/__tests__/fixtures/declarative-connection-mutation-transform/append-edge.graphql b/packages/relay-compiler/transforms/__tests__/fixtures/declarative-connection-mutation-transform/append-edge.graphql new file mode 100644 index 0000000000000..5df51f2d46c7e --- /dev/null +++ b/packages/relay-compiler/transforms/__tests__/fixtures/declarative-connection-mutation-transform/append-edge.graphql @@ -0,0 +1,13 @@ +mutation CommentCreateMutation( + $connections: [String!]! + $input: CommentCreateInput +) { + commentCreate(input: $input) { + feedbackCommentEdge @appendEdge(connections: $connections) { + cursor + node { + id + } + } + } +} diff --git a/packages/relay-runtime/util/NormalizationNode.js b/packages/relay-runtime/util/NormalizationNode.js index 916be74524e9c..f73e619329e6e 100644 --- a/packages/relay-runtime/util/NormalizationNode.js +++ b/packages/relay-runtime/util/NormalizationNode.js @@ -38,6 +38,7 @@ export type NormalizationLinkedHandle = {| // NOTE: this property is optional because it's expected to be rarely used +dynamicKey?: ?NormalizationArgument, +filters: ?$ReadOnlyArray, + +handleArgs?: $ReadOnlyArray, |}; export type NormalizationScalarHandle = {|