Skip to content

Commit 6735499

Browse files
committed
mutationMiddleware move all args into input! arg
if `input` exists add only `clientMutationId` to it and leave rest args untouched.
1 parent 7130137 commit 6735499

File tree

6 files changed

+155
-22
lines changed

6 files changed

+155
-22
lines changed

README.md

Lines changed: 13 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -13,29 +13,33 @@ import { TypeComposer } from 'graphql-compose';
1313
import { RootQueryType, UserType } from './my-graphq-object-types';
1414

1515
const rootQueryTypeComposer = new TypeComposer(RootQueryType);
16-
const userTypeComposer = new TypeComposer(UserType);
16+
const userTypeComposer = new TypeComposer(UserType);
1717

18-
// If passed RootQuery, then will be added only `node` field to this type.
18+
// If passed RootQuery, then will be added only `node` field to this type.
1919
// Via RootQuery.node you may find objects by globally unique ID among all types.
20-
composeWithRelay(rootQueryTypeComposer);
20+
composeWithRelay(rootQueryTypeComposer);
2121

2222
// Other types, like User, will be wrapped with middlewares that:
2323
// - add relay's id field. Field will be added or wrapped to return Relay's globally unique ID.
2424
// - for mutations will be added clientMutationId to input and output objects types
2525
// - this type will be added to NodeInterface for resolving via RootQuery.node
26-
composeWithRelay(userTypeComposer);
26+
composeWithRelay(userTypeComposer);
2727
```
2828
That's all!
2929

30-
No annoying `clientMutationId` manipulations (declaration, passthru, stripping from input).
31-
No manual adding/wrapping `id` field. This field returns globally unique ID among all types in format `base64(TypeName + ':' + recordId)`.
32-
All relay magic done internally by middleware for you.
30+
All mutations resolvers' arguments will be placed into `input` field, and added `clientMutationId`. If `input` fields already exists in resolver, then `clientMutationId` will be added to it, rest argument stays untouched. Accepted value via `args.input.clientMutationId` will be transfer to `payload.clientMutationId`, as Relay required it.
31+
32+
To all wrapped Types with Relay, will be added `id` field or wrapped, if it exist already. This field will return globally unique ID among all types in the following format `base64(TypeName + ':' + recordId)`.
33+
34+
For `RootQuery` will be added `node` field, that will resolve by globalId only that types, which you wrap with `composeWithRelay`.
35+
36+
All this annoying operations is too fatigue to do by hands. So this middleware done all Relay magic implicitly for you.
3337

3438
Requirements
3539
============
3640
Method `composeWithRelay` accept `TypeComposer` as input argument. So `TypeComposer` should meet following requirements:
37-
- has defined `recordIdFn` (function that from object gives you id)
38-
- should have `findById` resolver
41+
- has defined `recordIdFn` (function that from object of this type, returns you id for the globalId construction)
42+
- should have `findById` resolver (that will be used by `RootQuery.node`)
3943

4044
If something is missing `composeWithRelay` throws error.
4145

resources/watch.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,6 @@ function exec(command, options) {
1515
cmd: cmd,
1616
env: {
1717
...process.env,
18-
BABEL_ENV: 'cjs',
1918
},
2019
stdio: 'inherit'
2120
});
@@ -121,6 +120,7 @@ function runTests(filepaths) {
121120
'--compilers', 'js:babel-core/register',
122121
// '--reporter', 'progress',
123122
'--reporter', 'dot',
123+
'--require', 'babel-polyfill',
124124
'--require', './resources/mocha-bootload',
125125
].concat(
126126
allTests(filepaths) ?

src/__mocks__/userTypeComposer.js

Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -86,3 +86,72 @@ export const createOneResolver = new Resolver(userTypeComposer, {
8686
});
8787
},
8888
});
89+
90+
export const manyArgsWithInputResolver = new Resolver(userTypeComposer, {
91+
name: 'manyArgsWithInput',
92+
kind: 'mutation',
93+
outputType: new GraphQLObjectType({
94+
name: 'UserPayload',
95+
fields: {
96+
record: {
97+
type: UserType,
98+
},
99+
},
100+
}),
101+
args: {
102+
input: {
103+
name: 'input',
104+
type: new GraphQLInputObjectType({
105+
name: 'UserInput',
106+
fields: {
107+
name: {
108+
type: GraphQLString,
109+
},
110+
},
111+
}),
112+
},
113+
sort: {
114+
name: 'sort',
115+
type: GraphQLString,
116+
},
117+
limit: {
118+
name: 'limit',
119+
type: GraphQLInt,
120+
},
121+
},
122+
resolve: (resolveParams) => {
123+
return Promise.resolve({
124+
recordId: resolveParams.args.input.id,
125+
record: resolveParams.args && resolveParams.args.input || {},
126+
});
127+
},
128+
});
129+
130+
export const manyArgsWithoutInputResolver = new Resolver(userTypeComposer, {
131+
name: 'manyArgsWithoutInput',
132+
kind: 'mutation',
133+
outputType: new GraphQLObjectType({
134+
name: 'UserPayload',
135+
fields: {
136+
record: {
137+
type: UserType,
138+
},
139+
},
140+
}),
141+
args: {
142+
sort: {
143+
name: 'sort',
144+
type: GraphQLString,
145+
},
146+
limit: {
147+
name: 'limit',
148+
type: GraphQLInt,
149+
},
150+
},
151+
resolve: (resolveParams) => {
152+
return Promise.resolve({
153+
recordId: resolveParams.args.input.id,
154+
record: resolveParams.args && resolveParams.args.input || {},
155+
});
156+
},
157+
});

src/__tests__/mutationMiddleware-test.js

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,18 +6,52 @@ import { toGlobalId } from '../globalId';
66
import {
77
GraphQLString,
88
GraphQLID,
9+
GraphQLNonNull,
10+
getNamedType,
911
} from 'graphql';
1012

1113
describe('MutationMiddleware', () => {
1214
composeWithRelay(userTypeComposer);
1315
const fieldConfig = userTypeComposer.getResolver('createOne').getFieldConfig();
16+
const fieldConfigManyArgsWithInput
17+
= userTypeComposer.getResolver('manyArgsWithInput').getFieldConfig();
18+
const fieldConfigManyArgsWithoutInput
19+
= userTypeComposer.getResolver('manyArgsWithoutInput').getFieldConfig();
1420

1521
describe('args', () => {
1622
it('should add `clientMutationId` field to args.input', () => {
1723
const itc = new InputTypeComposer(fieldConfig.args.input.type);
1824
expect(itc.hasField('clientMutationId')).to.be.true;
1925
expect(itc.getFieldType('clientMutationId')).equal(GraphQLString);
2026
});
27+
28+
29+
it('should create required args.input! if not exists', () => {
30+
expect(fieldConfigManyArgsWithoutInput.args).property('input').to.be.ok;
31+
32+
expect(fieldConfigManyArgsWithoutInput.args)
33+
.deep.property('input.type').instanceof(GraphQLNonNull);
34+
});
35+
36+
it('should create args.input if not exists and move all args into it', () => {
37+
expect(fieldConfigManyArgsWithoutInput.args).to.have.all.keys(['input']);
38+
39+
const type = getNamedType(fieldConfigManyArgsWithoutInput.args.input.type);
40+
const itc = new InputTypeComposer(type);
41+
expect(itc.hasField('sort')).to.be.true;
42+
expect(itc.hasField('limit')).to.be.true;
43+
expect(itc.hasField('clientMutationId')).to.be.true;
44+
});
45+
46+
it('should leave other arg untouched if args.input exists', () => {
47+
expect(fieldConfigManyArgsWithInput.args).to.have.all.keys(['input', 'sort', 'limit']);
48+
49+
const type = getNamedType(fieldConfigManyArgsWithInput.args.input.type);
50+
const itc = new InputTypeComposer(type);
51+
expect(itc.hasField('sort')).to.be.false;
52+
expect(itc.hasField('limit')).to.be.false;
53+
expect(itc.hasField('clientMutationId')).to.be.true;
54+
});
2155
});
2256

2357
describe('outputType', () => {

src/composeWithRelay.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -56,7 +56,7 @@ export function composeWithRelay(
5656

5757
typeComposer.getResolvers().forEach(resolver => {
5858
if (resolver.kind === 'mutation') {
59-
resolver.addMiddleware(new MutationMiddleware(typeComposer));
59+
resolver.addMiddleware(new MutationMiddleware(typeComposer, resolver));
6060
}
6161
});
6262

src/mutationMiddleware.js

Lines changed: 37 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,14 @@
22
/* eslint-disable no-param-reassign */
33

44
import { ResolverMiddleware, TypeComposer, InputTypeComposer } from 'graphql-compose';
5-
import { GraphQLID, GraphQLString, GraphQLObjectType } from 'graphql';
5+
import {
6+
GraphQLID,
7+
GraphQLString,
8+
GraphQLObjectType,
9+
getNamedType,
10+
GraphQLInputObjectType,
11+
GraphQLNonNull,
12+
} from 'graphql';
613
import { toGlobalId } from './globalId';
714
import type {
815
ResolverMWArgsFn,
@@ -15,25 +22,44 @@ import type {
1522

1623
export default class MutationMiddleware extends ResolverMiddleware {
1724
// middleware has constructor
18-
// constructor(typeComposer, opts = {}) {
25+
// constructor(typeComposer, typeResolver, opts = {}) {
1926
// super(typeComposer, opts);
2027
// // some setup staff
2128
// }
2229

2330
args:ResolverMWArgs = (next: ResolverMWArgsFn) => (args) => {
2431
const nextArgs = next(args);
32+
let inputTC: InputTypeComposer;
33+
let newNextArgs = {};
34+
2535
if (nextArgs.input && nextArgs.input.type) {
26-
const itc = new InputTypeComposer(nextArgs.input.type);
27-
if (!itc.hasField('clientMutationId')) {
28-
itc.addField('clientMutationId', {
29-
type: GraphQLString,
30-
description: 'The client mutation ID used by clients like Relay to track the mutation. '
31-
+ 'If given, returned in the response payload of the mutation.',
32-
});
33-
}
36+
// pass args unchanged
37+
inputTC = new InputTypeComposer(getNamedType(nextArgs.input.type));
38+
newNextArgs = nextArgs;
39+
} else {
40+
// create input arg, and put into all current args
41+
inputTC = new InputTypeComposer(new GraphQLInputObjectType({
42+
name: `Relay${this.typeResolver.getNameCamelCase()}${this.typeComposer.getTypeName()}Input`,
43+
fields: nextArgs,
44+
}));
45+
newNextArgs = {
46+
input: {
47+
name: 'input',
48+
type: new GraphQLNonNull(inputTC.getType()),
49+
},
50+
};
51+
}
52+
53+
// add `clientMutationId` to args.input field
54+
if (!inputTC.hasField('clientMutationId')) {
55+
inputTC.addField('clientMutationId', {
56+
type: GraphQLString,
57+
description: 'The client mutation ID used by clients like Relay to track the mutation. '
58+
+ 'If given, returned in the response payload of the mutation.',
59+
});
3460
}
3561

36-
return nextArgs;
62+
return newNextArgs;
3763
};
3864

3965

0 commit comments

Comments
 (0)