diff --git a/packages/react-relay/__tests__/RelayResolverModel-test.js b/packages/react-relay/__tests__/RelayResolverModel-test.js
index aad8600fbed9c..74c3b5a8db72d 100644
--- a/packages/react-relay/__tests__/RelayResolverModel-test.js
+++ b/packages/react-relay/__tests__/RelayResolverModel-test.js
@@ -31,6 +31,7 @@ const RelayNetwork = require('relay-runtime/network/RelayNetwork');
const {graphql} = require('relay-runtime/query/GraphQLTag');
const {
addTodo,
+ changeDescription,
completeTodo,
resetStore,
} = require('relay-runtime/store/__tests__/resolvers/ExampleTodoStore');
@@ -279,6 +280,41 @@ describe.each([
expect(renderer.toJSON()).toEqual('Test todo - green');
});
+ test('read a field with arguments', () => {
+ function TodoComponentWithFieldWithArgumentsComponent(props: {
+ todoID: string,
+ }) {
+ const data = useClientQuery(
+ graphql`
+ query RelayResolverModelTestFieldWithArgumentsQuery($id: ID!) {
+ todo_model(todoID: $id) {
+ fancy_description {
+ text_with_prefix(prefix: "[x]")
+ }
+ }
+ }
+ `,
+ {id: props.todoID},
+ );
+ return data?.todo_model?.fancy_description?.text_with_prefix;
+ }
+
+ addTodo('Test todo');
+
+ const renderer = TestRenderer.create(
+
+
+ ,
+ );
+ expect(renderer.toJSON()).toEqual('[x] Test todo');
+
+ TestRenderer.act(() => {
+ changeDescription('todo-1', 'Changed todo description text');
+ jest.runAllImmediates();
+ });
+ expect(renderer.toJSON()).toEqual('[x] Changed todo description text');
+ });
+
test('read interface field', () => {
function TodoComponentWithInterfaceComponent(props: {todoID: string}) {
const data = useClientQuery(
diff --git a/packages/react-relay/__tests__/__generated__/RelayResolverModelTestFieldWithArgumentsQuery.graphql.js b/packages/react-relay/__tests__/__generated__/RelayResolverModelTestFieldWithArgumentsQuery.graphql.js
new file mode 100644
index 0000000000000..bc36a7870c421
--- /dev/null
+++ b/packages/react-relay/__tests__/__generated__/RelayResolverModelTestFieldWithArgumentsQuery.graphql.js
@@ -0,0 +1,292 @@
+/**
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * This source code is licensed under the MIT license found in the
+ * LICENSE file in the root directory of this source tree.
+ *
+ * @oncall relay
+ *
+ * @generated SignedSource<>
+ * @flow
+ * @lightSyntaxTransform
+ * @nogrep
+ */
+
+/* eslint-disable */
+
+'use strict';
+
+/*::
+import type { ClientRequest, ClientQuery } from 'relay-runtime';
+import type { DataID } from "relay-runtime";
+import type { TodoDescription____relay_model_instance$data } from "./../../../relay-runtime/store/__tests__/resolvers/__generated__/TodoDescription____relay_model_instance.graphql";
+import type { TodoModel____relay_model_instance$data } from "./../../../relay-runtime/store/__tests__/resolvers/__generated__/TodoModel____relay_model_instance.graphql";
+import {todo_model as queryTodoModelResolverType} from "../../../relay-runtime/store/__tests__/resolvers/QueryTodoModel.js";
+// Type assertion validating that `queryTodoModelResolverType` resolver is correctly implemented.
+// A type error here indicates that the type signature of the resolver module is incorrect.
+(queryTodoModelResolverType: (
+ args: {|
+ todoID: string,
+ |},
+) => ?{|
+ +id: DataID,
+|});
+import {text_with_prefix as todoDescriptionTextWithPrefixResolverType} from "../../../relay-runtime/store/__tests__/resolvers/TodoDescription.js";
+// Type assertion validating that `todoDescriptionTextWithPrefixResolverType` resolver is correctly implemented.
+// A type error here indicates that the type signature of the resolver module is incorrect.
+(todoDescriptionTextWithPrefixResolverType: (
+ __relay_model_instance: TodoDescription____relay_model_instance$data['__relay_model_instance'],
+ args: {|
+ prefix: string,
+ |},
+) => ?string);
+import {fancy_description as todoModelFancyDescriptionResolverType} from "../../../relay-runtime/store/__tests__/resolvers/TodoModel.js";
+// Type assertion validating that `todoModelFancyDescriptionResolverType` resolver is correctly implemented.
+// A type error here indicates that the type signature of the resolver module is incorrect.
+(todoModelFancyDescriptionResolverType: (
+ __relay_model_instance: TodoModel____relay_model_instance$data['__relay_model_instance'],
+) => ?TodoDescription);
+import type { TodoModel__fancy_description$normalization } from "./../../../relay-runtime/store/__tests__/resolvers/__generated__/TodoModel__fancy_description$normalization.graphql";
+import type { TodoDescription } from "../../../relay-runtime/store/__tests__/resolvers/TodoDescription.js";
+export type RelayResolverModelTestFieldWithArgumentsQuery$variables = {|
+ id: string,
+|};
+export type RelayResolverModelTestFieldWithArgumentsQuery$data = {|
+ +todo_model: ?{|
+ +fancy_description: ?{|
+ +text_with_prefix: ?string,
+ |},
+ |},
+|};
+export type RelayResolverModelTestFieldWithArgumentsQuery = {|
+ response: RelayResolverModelTestFieldWithArgumentsQuery$data,
+ variables: RelayResolverModelTestFieldWithArgumentsQuery$variables,
+|};
+*/
+
+var node/*: ClientRequest*/ = (function(){
+var v0 = [
+ {
+ "defaultValue": null,
+ "kind": "LocalArgument",
+ "name": "id"
+ }
+],
+v1 = [
+ {
+ "kind": "Variable",
+ "name": "todoID",
+ "variableName": "id"
+ }
+],
+v2 = [
+ {
+ "kind": "Literal",
+ "name": "prefix",
+ "value": "[x]"
+ }
+],
+v3 = {
+ "alias": null,
+ "args": null,
+ "kind": "ScalarField",
+ "name": "id",
+ "storageKey": null
+};
+return {
+ "fragment": {
+ "argumentDefinitions": (v0/*: any*/),
+ "kind": "Fragment",
+ "metadata": {
+ "hasClientEdges": true
+ },
+ "name": "RelayResolverModelTestFieldWithArgumentsQuery",
+ "selections": [
+ {
+ "kind": "ClientEdgeToClientObject",
+ "concreteType": "TodoModel",
+ "backingField": {
+ "alias": null,
+ "args": (v1/*: any*/),
+ "fragment": null,
+ "kind": "RelayResolver",
+ "name": "todo_model",
+ "resolverModule": require('./../../../relay-runtime/store/__tests__/resolvers/QueryTodoModel').todo_model,
+ "path": "todo_model"
+ },
+ "linkedField": {
+ "alias": null,
+ "args": (v1/*: any*/),
+ "concreteType": "TodoModel",
+ "kind": "LinkedField",
+ "name": "todo_model",
+ "plural": false,
+ "selections": [
+ {
+ "kind": "ClientEdgeToClientObject",
+ "concreteType": "TodoDescription",
+ "backingField": {
+ "alias": null,
+ "args": null,
+ "fragment": {
+ "args": null,
+ "kind": "FragmentSpread",
+ "name": "TodoModel____relay_model_instance"
+ },
+ "kind": "RelayResolver",
+ "name": "fancy_description",
+ "resolverModule": require('relay-runtime/experimental').weakObjectWrapper(require('relay-runtime/experimental').resolverDataInjector(require('./../../../relay-runtime/store/__tests__/resolvers/__generated__/TodoModel____relay_model_instance.graphql'), require('./../../../relay-runtime/store/__tests__/resolvers/TodoModel').fancy_description, '__relay_model_instance', false), '__relay_model_instance', false),
+ "path": "fancy_description",
+ "normalizationInfo": {
+ "concreteType": "TodoDescription",
+ "plural": false,
+ "normalizationNode": require('./../../../relay-runtime/store/__tests__/resolvers/__generated__/TodoModel__fancy_description$normalization.graphql')
+ }
+ },
+ "linkedField": {
+ "alias": null,
+ "args": null,
+ "concreteType": "TodoDescription",
+ "kind": "LinkedField",
+ "name": "fancy_description",
+ "plural": false,
+ "selections": [
+ {
+ "alias": null,
+ "args": (v2/*: any*/),
+ "fragment": {
+ "args": null,
+ "kind": "FragmentSpread",
+ "name": "TodoDescription____relay_model_instance"
+ },
+ "kind": "RelayResolver",
+ "name": "text_with_prefix",
+ "resolverModule": require('relay-runtime/experimental').resolverDataInjector(require('./../../../relay-runtime/store/__tests__/resolvers/__generated__/TodoDescription____relay_model_instance.graphql'), require('./../../../relay-runtime/store/__tests__/resolvers/TodoDescription').text_with_prefix, '__relay_model_instance', false),
+ "path": "text_with_prefix"
+ }
+ ],
+ "storageKey": null
+ }
+ }
+ ],
+ "storageKey": null
+ }
+ }
+ ],
+ "type": "Query",
+ "abstractKey": null
+ },
+ "kind": "Request",
+ "operation": {
+ "argumentDefinitions": (v0/*: any*/),
+ "kind": "Operation",
+ "name": "RelayResolverModelTestFieldWithArgumentsQuery",
+ "selections": [
+ {
+ "kind": "ClientEdgeToClientObject",
+ "backingField": {
+ "name": "todo_model",
+ "args": (v1/*: any*/),
+ "fragment": null,
+ "kind": "RelayResolver",
+ "storageKey": null,
+ "isOutputType": false
+ },
+ "linkedField": {
+ "alias": null,
+ "args": (v1/*: any*/),
+ "concreteType": "TodoModel",
+ "kind": "LinkedField",
+ "name": "todo_model",
+ "plural": false,
+ "selections": [
+ {
+ "kind": "ClientEdgeToClientObject",
+ "backingField": {
+ "name": "fancy_description",
+ "args": null,
+ "fragment": {
+ "kind": "InlineFragment",
+ "selections": [
+ {
+ "name": "__relay_model_instance",
+ "args": null,
+ "fragment": {
+ "kind": "InlineFragment",
+ "selections": [
+ (v3/*: any*/)
+ ],
+ "type": "TodoModel",
+ "abstractKey": null
+ },
+ "kind": "RelayResolver",
+ "storageKey": null,
+ "isOutputType": false
+ }
+ ],
+ "type": "TodoModel",
+ "abstractKey": null
+ },
+ "kind": "RelayResolver",
+ "storageKey": null,
+ "isOutputType": true
+ },
+ "linkedField": {
+ "alias": null,
+ "args": null,
+ "concreteType": "TodoDescription",
+ "kind": "LinkedField",
+ "name": "fancy_description",
+ "plural": false,
+ "selections": [
+ {
+ "name": "text_with_prefix",
+ "args": (v2/*: any*/),
+ "fragment": {
+ "kind": "InlineFragment",
+ "selections": [
+ {
+ "alias": null,
+ "args": null,
+ "kind": "ScalarField",
+ "name": "__relay_model_instance",
+ "storageKey": null
+ }
+ ],
+ "type": "TodoDescription",
+ "abstractKey": null
+ },
+ "kind": "RelayResolver",
+ "storageKey": "text_with_prefix(prefix:\"[x]\")",
+ "isOutputType": true
+ }
+ ],
+ "storageKey": null
+ }
+ },
+ (v3/*: any*/)
+ ],
+ "storageKey": null
+ }
+ }
+ ]
+ },
+ "params": {
+ "cacheID": "b10312a5cde1a4ef8e38b4b474a45635",
+ "id": null,
+ "metadata": {},
+ "name": "RelayResolverModelTestFieldWithArgumentsQuery",
+ "operationKind": "query",
+ "text": null
+ }
+};
+})();
+
+if (__DEV__) {
+ (node/*: any*/).hash = "d0daaf4fcd4eaf3bce605aa4775fff4a";
+}
+
+module.exports = ((node/*: any*/)/*: ClientQuery<
+ RelayResolverModelTestFieldWithArgumentsQuery$variables,
+ RelayResolverModelTestFieldWithArgumentsQuery$data,
+>*/);
diff --git a/packages/relay-runtime/store/__tests__/resolvers/ExampleTodoStore.js b/packages/relay-runtime/store/__tests__/resolvers/ExampleTodoStore.js
index e963885befd33..691dd61b1cfb7 100644
--- a/packages/relay-runtime/store/__tests__/resolvers/ExampleTodoStore.js
+++ b/packages/relay-runtime/store/__tests__/resolvers/ExampleTodoStore.js
@@ -39,6 +39,13 @@ type ACTION =
type: 'REMOVE_TODO',
payload: TodoID,
}
+ | {
+ type: 'CHANGE_TODO_DESCRIPTION',
+ payload: {
+ todoID: TodoID,
+ description: string,
+ },
+ }
| {
type: 'BLOCKED_BY',
payload: {
@@ -130,6 +137,20 @@ class TodoStore {
this._notify([action.payload.todoID]);
break;
}
+ case 'CHANGE_TODO_DESCRIPTION': {
+ this._state = this._state.map(todo => {
+ if (todo.todoID === action.payload.todoID) {
+ return {
+ ...todo,
+ description: action.payload.description,
+ };
+ } else {
+ return todo;
+ }
+ });
+ this._notify([action.payload.todoID]);
+ break;
+ }
default:
(action.type: empty);
}
@@ -217,6 +238,16 @@ function blockedBy(todoID: string, blockedBy: string) {
});
}
+function changeDescription(todoID: string, description: string) {
+ TODO_STORE.dispatch({
+ type: 'CHANGE_TODO_DESCRIPTION',
+ payload: {
+ todoID,
+ description,
+ },
+ });
+}
+
module.exports = {
TODO_STORE,
Selectors,
@@ -225,4 +256,5 @@ module.exports = {
completeTodo,
removeTodo,
blockedBy,
+ changeDescription,
};
diff --git a/packages/relay-runtime/store/__tests__/resolvers/TodoDescription.js b/packages/relay-runtime/store/__tests__/resolvers/TodoDescription.js
index 3dc51c3ed2ce9..8bc37399c426f 100644
--- a/packages/relay-runtime/store/__tests__/resolvers/TodoDescription.js
+++ b/packages/relay-runtime/store/__tests__/resolvers/TodoDescription.js
@@ -9,6 +9,9 @@
* @oncall relay
*/
+import type {TodoDescription__some_client_type_with_interface$normalization} from './__generated__/TodoDescription__some_client_type_with_interface$normalization.graphql';
+import type {TodoDescription__some_interface$normalization} from './__generated__/TodoDescription__some_interface$normalization.graphql';
+
/**
* @RelayResolver TodoDescription
* @weak
@@ -18,9 +21,6 @@ export opaque type TodoDescription = {
color: string,
};
-import type {TodoDescription__some_client_type_with_interface$normalization} from './__generated__/TodoDescription__some_client_type_with_interface$normalization.graphql';
-import type {TodoDescription__some_interface$normalization} from './__generated__/TodoDescription__some_interface$normalization.graphql';
-
// Public constructor for opaque `TodoDescription`.
// Other resolvers have to call this function to
// create an instance of `TodoDescription`.
@@ -41,6 +41,19 @@ function text(instance: ?TodoDescription): ?string {
return instance?.text;
}
+/**
+ * @RelayResolver TodoDescription.text_with_prefix(prefix: String!): String
+ */
+function text_with_prefix(
+ instance: ?TodoDescription,
+ args: {prefix: string},
+): ?string {
+ if (instance == null) {
+ return null;
+ }
+ return `${args.prefix} ${instance.text}`;
+}
+
/**
* @RelayResolver TodoDescription.color: RelayResolverValue
*/
@@ -75,6 +88,7 @@ function some_client_type_with_interface(
}
module.exports = {
+ text_with_prefix,
createTodoDescription,
text,
color,
diff --git a/packages/relay-runtime/store/experimental-live-resolvers/resolverDataInjector.js b/packages/relay-runtime/store/experimental-live-resolvers/resolverDataInjector.js
index 1ea8b4c9b57a6..0d161cb2c1980 100644
--- a/packages/relay-runtime/store/experimental-live-resolvers/resolverDataInjector.js
+++ b/packages/relay-runtime/store/experimental-live-resolvers/resolverDataInjector.js
@@ -16,9 +16,11 @@ import type {FragmentType} from '../RelayStoreTypes';
const {readFragment} = require('../ResolverFragments');
const invariant = require('invariant');
+type ResolverFn = ($FlowFixMe, ?$FlowFixMe) => mixed;
+
/**
*
- * This a High order function that returns a relay resolver that can read the data for
+ * This a higher order function that returns a relay resolver that can read the data for
* the fragment`.
*
* - fragment: contains fragment Reader AST with resolver's data dependencies.
@@ -28,16 +30,15 @@ const invariant = require('invariant');
* This will not call the `resolverFn` if the fragment data for it is null/undefined.
* The the compiler generates calls to this function, ensuring the correct set of arguments.
*/
-function resolverDataInjector<
- TFragmentType: FragmentType,
- TData: ?{...},
- TResolverFn: ($FlowFixMe, ?$FlowFixMe) => mixed,
->(
+function resolverDataInjector(
fragment: Fragment,
- resolverFn: TResolverFn,
+ // Resolvers have their own type assertions, we don't want to confuse users
+ // with a type error in their generated code at this point.
+ _resolverFn: $FlowFixMe,
fieldName?: string,
isRequiredField?: boolean,
): (fragmentKey: TFragmentType, args: mixed) => mixed {
+ const resolverFn: ResolverFn = _resolverFn;
return (fragmentKey: TFragmentType, args: mixed): mixed => {
const data = readFragment(fragment, fragmentKey);
if (fieldName != null) {