Mirror: The highly customizable and versatile GraphQL client with which you add on features like normalized caching as you grow.

(graphcache) - Fix updates config for renamed root schema types (#984)

* Fix store.updates not using mutation/subscription typenames

* Update SchemaPredicates for new updates structure

* Add test for altered root names

* Fix unsupported optional chaining in schemaPredicates

* Reformat schema code in store.ts

* Add changeset

Changed files
+86 -61
.changeset
exchanges
+7
.changeset/four-pigs-kneel.md
···
+
---
+
'@urql/exchange-graphcache': patch
+
---
+
+
Fix updaters config not working when Mutation/Subscription root names were altered.
+
For instance, a Mutation named `mutation_root` could cause `store.updates` to be misread and cause a
+
runtime error.
+14 -22
exchanges/graphcache/src/ast/schemaPredicates.ts
···
import { warn, invariant } from '../helpers/help';
import {
KeyingConfig,
-
UpdatesConfig,
+
UpdateResolver,
ResolverConfig,
OptimisticMutationConfig,
} from '../types';
···
export function expectValidUpdatesConfig(
schema: GraphQLSchema,
-
updates: UpdatesConfig
+
updates: Record<string, Record<string, UpdateResolver>>
): void {
if (process.env.NODE_ENV === 'production') {
return;
}
-
/* eslint-disable prettier/prettier */
-
const schemaMutations = schema.getMutationType()
-
? Object.keys((schema.getMutationType() as GraphQLObjectType).toConfig().fields)
-
: [];
-
const schemaSubscriptions = schema.getSubscriptionType()
-
? Object.keys((schema.getSubscriptionType() as GraphQLObjectType).toConfig().fields)
-
: [];
-
const givenMutations = updates.Mutation
-
? Object.keys(updates.Mutation)
-
: [];
-
const givenSubscriptions = updates.Subscription
-
? Object.keys(updates.Subscription)
-
: [];
-
/* eslint-enable prettier/prettier */
+
const mutation = schema.getMutationType();
+
const subscription = schema.getSubscriptionType();
+
const mutationFields = mutation ? mutation.getFields() : {};
+
const subscriptionFields = subscription ? subscription.getFields() : {};
+
const givenMutations = (mutation && updates[mutation.name]) || {};
+
const givenSubscription = (subscription && updates[subscription.name]) || {};
-
for (const givenMutation of givenMutations) {
-
if (schemaMutations.indexOf(givenMutation) === -1) {
+
for (const fieldName in givenMutations) {
+
if (mutationFields[fieldName] === undefined) {
warn(
'Invalid mutation field: `' +
-
givenMutation +
+
fieldName +
'` is not in the defined schema, but the `updates.Mutation` option is referencing it.',
21
);
}
}
-
for (const givenSubscription of givenSubscriptions) {
-
if (schemaSubscriptions.indexOf(givenSubscription) === -1) {
+
for (const fieldName in givenSubscription) {
+
if (subscriptionFields[fieldName] === undefined) {
warn(
'Invalid subscription field: `' +
-
givenSubscription +
+
fieldName +
'` is not in the defined schema, but the `updates.Subscription` option is referencing it.',
22
);
+39
exchanges/graphcache/src/store/store.test.ts
···
expect(warnMessage).toContain('https://bit.ly/2XbVrpR#24');
});
+
it('should use different rootConfigs', function () {
+
const fakeUpdater = jest.fn();
+
+
const store = new Store({
+
schema: minifyIntrospectionQuery(
+
require('../test-utils/altered_root_schema.json')
+
),
+
updates: {
+
Mutation: {
+
toggleTodo: fakeUpdater,
+
},
+
},
+
});
+
+
const mutationData = {
+
__typename: 'mutation_root',
+
toggleTodo: {
+
__typename: 'Todo',
+
id: 1,
+
},
+
};
+
write(store, { query: Todos }, todosData);
+
write(
+
store,
+
{
+
query: gql`
+
mutation {
+
toggleTodo(id: 1) {
+
id
+
}
+
}
+
`,
+
},
+
mutationData
+
);
+
+
expect(fakeUpdater).toBeCalledTimes(1);
+
});
+
it('should not warn for an introspection result root', function () {
// NOTE: Do not wrap this require in `minifyIntrospectionQuery`!
// eslint-disable-next-line
+23 -37
exchanges/graphcache/src/store/store.ts
···
Data,
QueryInput,
UpdatesConfig,
+
UpdateResolver,
OptimisticMutationConfig,
KeyingConfig,
DataFields,
} from '../types';
-
import { invariant } from '../helpers/help';
+
import { invariant } from '../helpers/help';
import { read, readFragment } from '../operations/query';
import { writeFragment, startWrite } from '../operations/write';
import { invalidateEntity } from '../operations/invalidate';
···
data: InMemoryData.InMemoryData;
resolvers: ResolverConfig;
-
updates: UpdatesConfig;
+
updates: Record<string, Record<string, UpdateResolver>>;
optimisticMutations: OptimisticMutationConfig;
keys: KeyingConfig;
schema?: GraphQLSchema;
···
this.optimisticMutations = opts.optimistic || {};
this.keys = opts.keys || {};
-
this.updates = {
-
Mutation: (opts.updates && opts.updates.Mutation) || {},
-
Subscription: (opts.updates && opts.updates.Subscription) || {},
-
} as UpdatesConfig;
-
let queryName = 'Query';
let mutationName = 'Mutation';
let subscriptionName = 'Subscription';
···
const queryType = schema.getQueryType();
const mutationType = schema.getMutationType();
const subscriptionType = schema.getSubscriptionType();
-
if (queryType) queryName = queryType.name;
-
if (mutationType) mutationName = mutationType.name;
-
if (subscriptionType) subscriptionName = subscriptionType.name;
-
-
if (process.env.NODE_ENV !== 'production') {
-
if (this.keys) {
-
SchemaPredicates.expectValidKeyingConfig(this.schema, this.keys);
-
}
-
-
const hasUpdates =
-
Object.keys(this.updates.Mutation).length > 0 ||
-
Object.keys(this.updates.Subscription).length > 0;
-
if (hasUpdates) {
-
SchemaPredicates.expectValidUpdatesConfig(this.schema, this.updates);
-
}
-
-
if (this.resolvers) {
-
SchemaPredicates.expectValidResolversConfig(
-
this.schema,
-
this.resolvers
-
);
-
}
-
-
if (this.optimisticMutations) {
-
SchemaPredicates.expectValidOptimisticMutationsConfig(
-
this.schema,
-
this.optimisticMutations
-
);
-
}
-
}
+
queryName = queryType ? queryType.name : queryName;
+
mutationName = mutationType ? mutationType.name : mutationName;
+
subscriptionName = subscriptionType
+
? subscriptionType.name
+
: subscriptionName;
}
+
this.updates = {
+
[mutationName]: (opts.updates && opts.updates.Mutation) || {},
+
[subscriptionName]: (opts.updates && opts.updates.Subscription) || {},
+
};
+
this.rootFields = {
query: queryName,
mutation: mutationName,
···
};
this.data = InMemoryData.make(queryName);
+
+
if (this.schema && process.env.NODE_ENV !== 'production') {
+
SchemaPredicates.expectValidKeyingConfig(this.schema, this.keys);
+
SchemaPredicates.expectValidUpdatesConfig(this.schema, this.updates);
+
SchemaPredicates.expectValidResolversConfig(this.schema, this.resolvers);
+
SchemaPredicates.expectValidOptimisticMutationsConfig(
+
this.schema,
+
this.optimisticMutations
+
);
+
}
}
keyOfField = keyOfField;
+3 -2
exchanges/graphcache/src/test-utils/altered_root_schema.json
···
"__typename": "__Type"
},
"mutationType": {
-
"name": "Mutation"
+
"name": "mutation_root",
+
"__typename": "__Type"
},
"subscriptionType": null,
"types": [
···
},
{
"kind": "OBJECT",
-
"name": "Mutation",
+
"name": "mutation_root",
"fields": [
{
"name": "toggleTodo",