Skip to content

Commit

Permalink
implement deleteEdge store updater directive (#3177)
Browse files Browse the repository at this point in the history
Summary:
_This supersedes #3148 which I've now closed._

This implements a new declarative for updating the store declaratively after mutations called `deleteEdge`. As the name hints at, this particular directive allows you to remove a node's edge from the provided connections. Please note that this directive does not _delete the node_, only edge(s) for the node. There's `deleteRecord` already for deleting a record, which can be combined with this directive.

It's intended to be used like this:

```
mutation DeleteComment($input: DeleteCommentInput!, $connections: [String!]!) {
  deleteComment(input: $input) {
    deletedCommentId deleteEdge(connections: $connections)
    # This will delete any edge for the node with id `deletedCommentId` from the
    # connections provided through `$connections`
  }
}
```

It works for single IDs (as demonstrated above) as well as a list of IDs.

Pull Request resolved: #3177

Reviewed By: kassens

Differential Revision: D23652072

Pulled By: tyao1

fbshipit-source-id: 0d41dd0bb4950276bd8a8fb9178b1ee9d0a4337d
  • Loading branch information
zth authored and facebook-github-bot committed Sep 30, 2020
1 parent de38d41 commit 01d65b3
Show file tree
Hide file tree
Showing 13 changed files with 675 additions and 170 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -390,7 +390,7 @@ function generateScalarField(node): Array<NormalizationSelection> {
[handle.dynamicKey.loc],
);
}
return {
const nodeHandle = {
alias: node.alias === node.name ? null : node.alias,
args: generateArgs(node.args),
filters: handle.filters,
Expand All @@ -399,6 +399,13 @@ function generateScalarField(node): Array<NormalizationSelection> {
kind: 'ScalarHandle',
name: node.name,
};

if (handle.handleArgs != null) {
// $FlowFixMe handleArgs exists in Handle
nodeHandle.handleArgs = generateArgs(handle.handleArgs);
}

return nodeHandle;
})) ||
[];
let field = {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6098,6 +6098,238 @@ Fragment {
}
`;

exports[`compileRelayArtifacts matches expected output: delete-edge.graphql 1`] = `
~~~~~~~~~~ INPUT ~~~~~~~~~~
mutation CommentDeleteMutation(
$input: CommentDeleteInput
$connections: [String!]!
) {
commentDelete(input: $input) {
deletedCommentId @deleteEdge(connections: $connections)
}
}

~~~~~~~~~~ OUTPUT ~~~~~~~~~~
Request {
fragment: Fragment {
argumentDefinitions: [
LocalArgument {
defaultValue: null,
name: "connections",
},
LocalArgument {
defaultValue: null,
name: "input",
},
],
metadata: null,
name: "CommentDeleteMutation",
selections: [
LinkedField {
alias: null,
args: [
Variable {
name: "input",
variableName: "input",
},
],
concreteType: "CommentDeleteResponsePayload",
name: "commentDelete",
plural: false,
selections: [
ScalarField {
alias: null,
args: null,
name: "deletedCommentId",
storageKey: null,
},
],
storageKey: null,
},
],
type: "Mutation",
abstractKey: null,
},
operation: Operation {
argumentDefinitions: [
LocalArgument {
defaultValue: null,
name: "input",
},
LocalArgument {
defaultValue: null,
name: "connections",
},
],
name: "CommentDeleteMutation",
selections: [
LinkedField {
alias: null,
args: [
Variable {
name: "input",
variableName: "input",
},
],
concreteType: "CommentDeleteResponsePayload",
name: "commentDelete",
plural: false,
selections: [
ScalarField {
alias: null,
args: null,
name: "deletedCommentId",
storageKey: null,
},
ScalarHandle {
alias: null,
args: null,
filters: null,
handle: "deleteEdge",
key: "",
name: "deletedCommentId",
handleArgs: [
Variable {
name: "connections",
variableName: "connections",
},
],
},
],
storageKey: null,
},
],
},
}

QUERY:

mutation CommentDeleteMutation(
$input: CommentDeleteInput
) {
commentDelete(input: $input) {
deletedCommentId
}
}

`;

exports[`compileRelayArtifacts matches expected output: delete-edge-plural.graphql 1`] = `
~~~~~~~~~~ INPUT ~~~~~~~~~~
mutation CommentsDeleteMutation(
$input: CommentsDeleteInput
$connections: [String!]!
) {
commentsDelete(input: $input) {
deletedCommentIds @deleteEdge(connections: $connections)
}
}

~~~~~~~~~~ OUTPUT ~~~~~~~~~~
Request {
fragment: Fragment {
argumentDefinitions: [
LocalArgument {
defaultValue: null,
name: "connections",
},
LocalArgument {
defaultValue: null,
name: "input",
},
],
metadata: null,
name: "CommentsDeleteMutation",
selections: [
LinkedField {
alias: null,
args: [
Variable {
name: "input",
variableName: "input",
},
],
concreteType: "CommentsDeleteResponsePayload",
name: "commentsDelete",
plural: false,
selections: [
ScalarField {
alias: null,
args: null,
name: "deletedCommentIds",
storageKey: null,
},
],
storageKey: null,
},
],
type: "Mutation",
abstractKey: null,
},
operation: Operation {
argumentDefinitions: [
LocalArgument {
defaultValue: null,
name: "input",
},
LocalArgument {
defaultValue: null,
name: "connections",
},
],
name: "CommentsDeleteMutation",
selections: [
LinkedField {
alias: null,
args: [
Variable {
name: "input",
variableName: "input",
},
],
concreteType: "CommentsDeleteResponsePayload",
name: "commentsDelete",
plural: false,
selections: [
ScalarField {
alias: null,
args: null,
name: "deletedCommentIds",
storageKey: null,
},
ScalarHandle {
alias: null,
args: null,
filters: null,
handle: "deleteEdge",
key: "",
name: "deletedCommentIds",
handleArgs: [
Variable {
name: "connections",
variableName: "connections",
},
],
},
],
storageKey: null,
},
],
},
}

QUERY:

mutation CommentsDeleteMutation(
$input: CommentsDeleteInput
) {
commentsDelete(input: $input) {
deletedCommentIds
}
}

`;

exports[`compileRelayArtifacts matches expected output: explicit-null-argument.graphql 1`] = `
~~~~~~~~~~ INPUT ~~~~~~~~~~
query Test {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
mutation CommentsDeleteMutation(
$input: CommentsDeleteInput
$connections: [String!]!
) {
commentsDelete(input: $input) {
deletedCommentIds @deleteEdge(connections: $connections)
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
mutation CommentDeleteMutation(
$input: CommentDeleteInput
$connections: [String!]!
) {
commentDelete(input: $input) {
deletedCommentId @deleteEdge(connections: $connections)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ const {createUserError} = require('../core/CompilerError');
const {ConnectionInterface} = require('relay-runtime');

const DELETE_RECORD = 'deleteRecord';
const DELETE_EDGE = 'deleteEdge';
const APPEND_EDGE = 'appendEdge';
const PREPEND_EDGE = 'prependEdge';
const APPEND_NODE = 'appendNode';
Expand All @@ -31,6 +32,9 @@ const LINKED_FIELD_DIRECTIVES = [

const SCHEMA_EXTENSION = `
directive @${DELETE_RECORD} on FIELD
directive @${DELETE_EDGE}(
connections: [String!]!
) on FIELD
directive @${APPEND_EDGE}(
connections: [String!]!
) on FIELD
Expand Down Expand Up @@ -73,34 +77,49 @@ function visitScalarField(field: ScalarField): ScalarField {
[linkedFieldDirective.loc],
);
}
const deleteDirective = field.directives.find(
const deleteNodeDirective = field.directives.find(
directive => directive.name === DELETE_RECORD,
);
if (deleteDirective == null) {
const deleteEdgeDirective = field.directives.find(
directive => directive.name === DELETE_EDGE,
);
if (deleteNodeDirective != null && deleteEdgeDirective != null) {
throw createUserError(
`Both @deleteNode and @deleteEdge are used on field '${field.name}'. Only one directive is supported for now.`,
[deleteNodeDirective.loc, deleteEdgeDirective.loc],
);
}
const targetDirective = deleteNodeDirective ?? deleteEdgeDirective;
if (targetDirective == null) {
return field;
}

const schema = this.getContext().getSchema();

if (!schema.isId(schema.getRawType(field.type))) {
throw createUserError(
`Invalid use of @${DELETE_RECORD} on field '${
`Invalid use of @${targetDirective.name} on field '${
field.name
}'. Expected field to return an ID or list of ID values, got ${schema.getTypeString(
field.type,
)}.`,
[deleteDirective.loc],
[targetDirective.loc],
);
}
const connectionsArg = targetDirective.args.find(
arg => arg.name === 'connections',
);
const handle: Handle = {
name: DELETE_RECORD,
name: targetDirective.name,
key: '',
dynamicKey: null,
filters: null,
handleArgs: connectionsArg ? [connectionsArg] : undefined,
};
return {
...field,
directives: field.directives.filter(
directive => directive !== deleteDirective,
directive => directive !== targetDirective,
),
handles: field.handles ? [...field.handles, handle] : [handle],
};
Expand Down
Loading

0 comments on commit 01d65b3

Please sign in to comment.