function actionsToVariables(actions: Array<Action<ActionType>>, schema: CannerSchema) {
  const variables = {payload: {}, where: {}};

  actions.forEach(action => {
    let {path = '', value, id, relation, key, transformGqlPayload} = action.payload;
    const relationField = genRelationField(schema, key);
    const schemaWithPath = addPath(schema[key], '');
    const jsonPath = findSchema(schemaWithPath, s => s.type === 'json')
      .map(v => v.path);
    value = parseArrayToSet(value, relationField, jsonPath, path ? `${key}/${path}` : key);
    
    switch(action.type) {
      case 'CREATE_ARRAY': {
        // remove null relation
        const ensureValue = pickBy(value, (v, k) => v !== null && relationField.indexOf(k) === -1);
        merge(variables.payload, ensureValue);
        break;
      }
      case 'UPDATE_ARRAY':
      case 'UPDATE_OBJECT': {
        merge(variables.payload, value);
        merge(variables.where, {id});
        break;
      }
      case 'CONNECT': {
        if (relation && relation.type === 'toMany') {
          update(variables.payload, path.split('/').concat('connect'), arr => {
            let connectValue = {id: value.id};
            /* transformGqlPayload is a experimental usage */
            if (transformGqlPayload) {
              connectValue = transformGqlPayload(connectValue, action);
            }
            if (!arr || !arr.concat) {
              return [connectValue];
            }
            return arr.concat(connectValue);
          });
        } else {
          set(variables.payload, path.split('/').concat('connect'), {id: value.id});
        }
        if (id) {
          merge(variables.where, {id});
        }
        break;
      }
      case 'CREATE_AND_CONNECT': {
        if (relation && relation.type === 'toMany') {
          update(variables.payload, path.split('/').concat('create'), arr => (arr || []).concat(value));
        } else {
          set(variables.payload, path.split('/').concat('create'), value);
        }
        merge(variables.where, {id});
        break;
      }
      case 'DISCONNECT':
        if (relation && relation.type === 'toMany') {
          update(variables.payload, path.split('/').concat('disconnect'), arr => (arr || []).concat({id: value.id}));
        } else {
          set(variables.payload, path.split('/').concat('disconnect'), true);
        }
        if (id) {
          merge(variables.where, {id});
        }
        break;
      case 'DISCONNECT_AND_DELETE':
        if (relation && relation.type === 'toMany') {
          update(variables.payload, path.split('/').concat('delete'), arr => (arr || []).concat(value));
        } else {
          set(variables.payload, path.split('/').concat('delete'), true);
        }
        merge(variables.where, {id});
        break;
      case 'DELETE_ARRAY':
        merge(variables.where, {id});
        break;
      default:
        break;
    }
  });
  variables.payload = removeTypename(variables.payload);
  if (isPlainObject(variables.payload)) {
    delete variables.payload.id;
  }
  return variables;
}
change actions to variables which is the argument of graphql mutation