Skip to content

Commit

Permalink
Declarative @deleteFromStore in mutation
Browse files Browse the repository at this point in the history
Reviewed By: josephsavona

Differential Revision: D22328497

fbshipit-source-id: 8946e68b991fb2d81307d10d3d31dbeb1595322e
  • Loading branch information
tyao1 authored and facebook-github-bot committed Jul 2, 2020
1 parent 232dd95 commit 07ccab7
Show file tree
Hide file tree
Showing 10 changed files with 492 additions and 3 deletions.
9 changes: 6 additions & 3 deletions packages/relay-compiler/core/RelayIRTransforms.js
Expand Up @@ -15,6 +15,7 @@
const ApplyFragmentArgumentTransform = require('../transforms/ApplyFragmentArgumentTransform');
const ClientExtensionsTransform = require('../transforms/ClientExtensionsTransform');
const ConnectionTransform = require('../transforms/ConnectionTransform');
const DeclarativeConnectionMutationTransform = require('../transforms/DeclarativeConnectionMutationTransform');
const DeferStreamTransform = require('../transforms/DeferStreamTransform');
const DisallowIdAsAlias = require('../transforms/DisallowIdAsAlias');
const DisallowTypenameOnRoot = require('../transforms/DisallowTypenameOnRoot');
Expand Down Expand Up @@ -47,12 +48,13 @@ import type {IRTransform} from './CompilerContext';
// Transforms applied to the code used to process a query response.
const relaySchemaExtensions: $ReadOnlyArray<string> = [
ConnectionTransform.SCHEMA_EXTENSION,
DeclarativeConnectionMutationTransform.SCHEMA_EXTENSION,
InlineDataFragmentTransform.SCHEMA_EXTENSION,
MatchTransform.SCHEMA_EXTENSION,
RelayDirectiveTransform.SCHEMA_EXTENSION,
RefetchableFragmentTransform.SCHEMA_EXTENSION,
TestOperationTransform.SCHEMA_EXTENSION,
InlineDataFragmentTransform.SCHEMA_EXTENSION,
RelayDirectiveTransform.SCHEMA_EXTENSION,
RelayFlowGenerator.SCHEMA_EXTENSION,
TestOperationTransform.SCHEMA_EXTENSION,
ValidateUnusedVariablesTransform.SCHEMA_EXTENSION,
];

Expand Down Expand Up @@ -86,6 +88,7 @@ const relayQueryTransforms: $ReadOnlyArray<IRTransform> = [
ApplyFragmentArgumentTransform.transform,
ValidateGlobalVariablesTransform.transform,
GenerateIDFieldTransform.transform,
DeclarativeConnectionMutationTransform.transform,
];

// Transforms applied to the code used to process a query response.
Expand Down
@@ -0,0 +1,96 @@
/**
* Copyright (c) Facebook, Inc. and its affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
* @flow strict-local
* @format
*/

// flowlint ambiguous-object-type:error

'use strict';

const IRTransformer = require('../core/IRTransformer');

const {createUserError} = require('../core/CompilerError');

const DELETE_RECORD = 'deleteRecord';
const SCHEMA_EXTENSION = `
directive @${DELETE_RECORD} on FIELD
`;

import type CompilerContext from '../core/CompilerContext';
import type {ScalarField, Root, Handle, LinkedField} from '../core/IR';

function transform(context: CompilerContext): CompilerContext {
return IRTransformer.transform(context, {
Root: visitRoot,
ScalarField: visitScalarField,
LinkedField: visitLinkedField,
SplitOperation: skip,
Fragment: skip,
});
}

function skip<T>(node: T): T {
return node;
}

function visitRoot(root: Root): Root {
if (root.operation !== 'mutation') {
return root;
}
return this.traverse(root);
}

function visitScalarField(field: ScalarField): ScalarField {
const deleteDirective = field.directives.find(
directive => directive.name === DELETE_RECORD,
);
if (deleteDirective == null) {
return field;
}
const schema = this.getContext().getSchema();
if (!schema.isId(field.type)) {
throw createUserError(
`Invalid use of @${DELETE_RECORD} on field '${
field.name
}'. Expected field type ID, got ${schema.getTypeString(field.type)}.`,
[deleteDirective.loc],
);
}
const handle: Handle = {
name: DELETE_RECORD,
key: '',
dynamicKey: null,
filters: null,
};
return {
...field,
directives: field.directives.filter(
directive => directive !== deleteDirective,
),
handles: field.handles ? [...field.handles, handle] : [handle],
};
}

function visitLinkedField(field: LinkedField): LinkedField {
const transformedField = this.traverse(field);
const deleteDirective = transformedField.directives.find(
directive => directive.name === DELETE_RECORD,
);
if (deleteDirective != null) {
throw createUserError(
`Invalid use of @${deleteDirective.name} on scalar field '${transformedField.name}'`,
[deleteDirective.loc],
);
}
return transformedField;
}

module.exports = {
SCHEMA_EXTENSION,
transform,
};
@@ -0,0 +1,40 @@
/**
* Copyright (c) Facebook, Inc. and its affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
* @flow strict-local
* @format
* @emails oncall+relay
*/

// flowlint ambiguous-object-type:error

'use strict';

const CompilerContext = require('../../core/CompilerContext');
const DeclarativeConnectionMutationTransform = require('../DeclarativeConnectionMutationTransform');
const IRPrinter = require('../../core/IRPrinter');

const {
TestSchema,
generateTestsFromFixtures,
parseGraphQLText,
} = require('relay-test-utils-internal');

generateTestsFromFixtures(
`${__dirname}/fixtures/declarative-connection-mutation-transform`,
text => {
const extendedSchema = TestSchema.extend([
DeclarativeConnectionMutationTransform.SCHEMA_EXTENSION,
]);
const {definitions} = parseGraphQLText(extendedSchema, text);
return new CompilerContext(extendedSchema)
.addAll(definitions)
.applyTransforms([DeclarativeConnectionMutationTransform.transform])
.documents()
.map(doc => IRPrinter.print(extendedSchema, doc))
.join('\n');
},
);
@@ -0,0 +1,42 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP

exports[`matches expected output: delete-from-store.graphql 1`] = `
~~~~~~~~~~ INPUT ~~~~~~~~~~
mutation CommentDeleteMutation($input: CommentDeleteInput) {
commentDelete(input: $input) {
deletedCommentId @deleteRecord
}
}
~~~~~~~~~~ OUTPUT ~~~~~~~~~~
mutation CommentDeleteMutation(
$input: CommentDeleteInput
) {
commentDelete(input: $input) {
deletedCommentId @__clientField(handle: "deleteRecord")
}
}
`;

exports[`matches expected output: delete-on-unspported-type.invalid.graphql 1`] = `
~~~~~~~~~~ INPUT ~~~~~~~~~~
# expected-to-throw
mutation CommentDeleteMutation($input: CommentDeleteInput) {
commentDelete(input: $input) {
__typename @deleteRecord
}
}
~~~~~~~~~~ OUTPUT ~~~~~~~~~~
THROWN EXCEPTION:
Invalid use of @deleteRecord on field '__typename'. Expected field type ID, got String!.
Source: GraphQL request (4:16)
3: commentDelete(input: $input) {
4: __typename @deleteRecord
^
5: }
`;
@@ -0,0 +1,5 @@
mutation CommentDeleteMutation($input: CommentDeleteInput) {
commentDelete(input: $input) {
deletedCommentId @deleteRecord
}
}
@@ -0,0 +1,6 @@
# expected-to-throw
mutation CommentDeleteMutation($input: CommentDeleteInput) {
commentDelete(input: $input) {
__typename @deleteRecord
}
}
Expand Up @@ -13,6 +13,7 @@
'use strict';

const ConnectionHandler = require('./connection/ConnectionHandler');
const MutationHandlers = require('./connection/MutationHandlers');

const invariant = require('invariant');

Expand All @@ -23,6 +24,8 @@ function RelayDefaultHandlerProvider(handle: string): Handler {
switch (handle) {
case 'connection':
return ConnectionHandler;
case 'deleteRecord':
return MutationHandlers.DeleteRecordHandler;
}
invariant(
false,
Expand Down
34 changes: 34 additions & 0 deletions packages/relay-runtime/handlers/connection/MutationHandlers.js
@@ -0,0 +1,34 @@
/**
* Copyright (c) Facebook, Inc. and its affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
* @flow
* @format
*/

// flowlint ambiguous-object-type:error

'use strict';

import type {
HandleFieldPayload,
RecordSourceProxy,
} from '../../store/RelayStoreTypes';

const DeleteRecordHandler = {
update: (store: RecordSourceProxy, payload: HandleFieldPayload) => {
const record = store.get(payload.dataID);
if (record != null) {
const id = record.getValue(payload.fieldKey);
if (typeof id === 'string') {
store.delete(id);
}
}
},
};

module.exports = {
DeleteRecordHandler,
};
2 changes: 2 additions & 0 deletions packages/relay-runtime/index.js
Expand Up @@ -15,6 +15,7 @@
const ConnectionHandler = require('./handlers/connection/ConnectionHandler');
const ConnectionInterface = require('./handlers/connection/ConnectionInterface');
const GraphQLTag = require('./query/GraphQLTag');
const MutationHandlers = require('./handlers/connection/MutationHandlers');
const PreloadableQueryRegistry = require('./query/PreloadableQueryRegistry');
const RelayConcreteNode = require('./util/RelayConcreteNode');
const RelayConcreteVariables = require('./store/RelayConcreteVariables');
Expand Down Expand Up @@ -261,6 +262,7 @@ module.exports = {
// Extensions
DefaultHandlerProvider: RelayDefaultHandlerProvider,
ConnectionHandler,
MutationHandlers,
VIEWER_ID: ViewerPattern.VIEWER_ID,
VIEWER_TYPE: ViewerPattern.VIEWER_TYPE,

Expand Down

0 comments on commit 07ccab7

Please sign in to comment.