How to resolve GraphQl mutations that have an array as the input - node.js

So am new to GraphQL and I'm trying to resolve a mutation that has an Input type of an array. I'm getting this error
{
"data": {
"createSub": null
},
"errors": [
{
"message": "Variable '$data' expected value of type 'SubCreateInput!' but got: {\"apps\":[{\"name\":\"ma\",\"package\":\"me\",\"running\":true,\"isSysytem\":true}]}. Reason: 'apps' Expected 'AppListCreateManyInput', found not an object. (line 1, column 11):\nmutation ($data: SubCreateInput!) {\n ^",
"locations": [
{
"line": 2,
"column": 3
}
],
"path": [
"createSub"
]
}
]
}
This is my schema
type Mutation {
createSub(input:subInput): Sub
}
input subInput{
apps: [AppListInput]
}
type Sub{
id: ID!
apps: [AppList]
}
type AppList {
id: ID!
name: String
package: String
running: Boolean
isSysytem: Boolean
}
input AppListInput {
name: String
package: String
running: Boolean
isSysytem: Boolean
}
And this is my resolver
function createSub(root, args, context) {
return context.prisma.createSub({
apps: args.input.apps
})
}
The mutation/payload am sending on the Graphql playground is this
mutation{
createSub( input:{
apps: [{
name: "ma"
package: "me"
running: true
isSysytem: true
}],
})
{
apps{
name
}
}
}
When I console.log(args.input.apps) I'm getting this
[ [Object: null prototype] { name: 'ma', package: 'me', running: true, isSysytem: true } ]
This is the input AppListCreateManyInput generated in the schema
input AppListCreateManyInput {
create: [AppListCreateInput!]
connect: [AppListWhereUniqueInput!]
}
What could I be missing please?

You need to provide the appropriate object to createSub, as shown here. Because apps is a relation, you can't just pass an array of apps in -- after all, when creating the Sub, you may want to either create new apps and relate them to the newly created Sub, or simply relate existing apps to it.
return context.prisma.createSub({
apps: {
create: args.input.apps, // create takes an array of apps to create
}
})
If you wanted to connect existing apps instead of creating new ones, you would use connect instead of create and pass in an object specifying a where condition instead of an array.

Related

How can I cast a string to number in an response using axios?

I am using Axios to execute a GET request to a public API. This API return a numeric value as string. I need to cast this value a numeric value. My application runs using tsc, so I expect the result of my object to be a numeric value, but it's not.
Axios Response
[
{
"name": "foo1",
"value": "8123.3000"
},
{
"name": "foo2",
"value": "5132.2003"
},
{
"name": "foo3",
"value": "622.0000"
}
]
Expected Output
[
{
"name": "foo1",
"value": 8123.3
},
{
"name": "foo2",
"value": 5132.2003
},
{
"name": "foo3",
"value": 622
}
]
My code is very simple,
interface MyObj {
myString: string;
myNumber: number;
}
(async () => {
let { data }: AxiosResponse<MyObj> = await axios.get<MyObj>("/public/data");
console.log(data);
})();
I try to use interface, class, the interface Number. Nothing worked.
I leave an example of code to try it.
How can I get the expected output without manually converting each value one by one?
Axios does not change the type of properties in the response. Please verify that the server does not send you the wrong types.
Edit
From your comment, it seems that the server sends you the vslue as string instead of as number. In this case I would suggest working with Ajv (https://github.com/ajv-validator/ajv) so you can create a schema that describes how the response looks like. Ajv cn also transform the value from string to number for you:
const Ajv = require('ajv')
const ajv = new Ajv({
// allow chaning the type of some values from type X to type Y. depends on the source and target type:
// https://ajv.js.org/guide/modifying-data.html#coercing-data-types
// X => Y rules: https://ajv.js.org/coercion.html
coerceTypes: true,
})
const schema = {
type: 'array',
items: {
type: 'object',
properties: {
name: { type: 'string' },
value: { type: 'number' },
},
required: ['name', 'value'],
additionalProperties: false,
},
}
const data = { name: 1, value: '1.1' }
console.log(typeof data.value === 'number') // false!
const valid = ajv.validate(schema, data)
if (!valid) {
console.log(ajv.errors)
process.exit(1)
}
console.log(typeof data.value === 'number') // true!
When declaring that your axios request returns a certain type; this will be checked at compile time and syntax checking. It does not however do this at runtime. If you know that the axios request is returning something different than your interface, you need to convert to that format first. You can do this using the second argument of the JSON.parse function like so:
interface Item {
name: string;
value: number;
}
let responseString = `
[
{
"name": "foo1",
"value": "8123.3000"
},
{
"name": "foo2",
"value": "5132.2003"
},
{
"name": "foo3",
"value": "622.0000"
}
]`
const items: Item[] = JSON.parse(responseString, (key, value) => {
const propertiesToCast = ["value"] // Which properties should be converted from string to number
if (propertiesToCast.includes(key)) {
return parseFloat(value)
}
return value
});
console.log(items)

How to save dynamic number of variables into database using graphql?

I'm trying to mutate and query dynamic variables. The user has the choice to add as many variables as they want before sending them off to the server. For example, my app is a productivity app that allows a user to add as many metrics as they want to track their goal so if "Gym" is their goal, the metrics would be "running", "bench press", etc. My problem is, I'm unsure how to save them in the database since there is no pre-configured Schema for these user-created variables.
I've managed to send the variables to the back end using the following:
mutation CreateGoal ($title: String!, $description: String, $metric: [Json!]) {
createGoal(
data: {
title: $title
description: $description
metric: { set: $metric }
}
){
id
}
}
Schema:
type Mutation {
createGoal(data: CreateGoalInput!): Goal!
}
input CreateGoalInput {
title: String!
description: String
metric: GoalCreatemetricInput
}
input GoalCreatemetricInput {
set: [Json!]
}
Once the variables arrive in the resolver, it's in the Json format:
{ set: [ 'running', 'bench press' ] }
Normally, I'd simply save the variables through Prisma:
async createGoal(parent, { data }, { request, prisma }, info) {
const { title, description, metric } = data && data
return prisma.mutation.createGoal({
data: {
user: {
connect: {
email: user.email
}
},
title,
description,
}
}, info)
},
However, since the number of variables are unknown, how do I save 'metric' into my database?
If I were to try the following:
async createGoal(parent, { data }, { request, prisma }, info) {
const { title, description, metric } = data && data
return prisma.mutation.createGoal({
data: {
user: {
connect: {
email: user.email
}
},
title,
description,
metric,
}
}, info)
},
I get the error:
Error: Variable "$_v0_data" got invalid value [ "running", "bench
press" ] at "_v0_data.metric"; Field "0" is not defined by type
GoalCreatemetricInput.
If I were to try:
async createGoal(parent, { data }, { request, prisma }, info) {
const { title, description, metric } = data && data
return prisma.mutation.createGoal({
data: {
user: {
connect: {
email: user.email
}
},
title,
description,
metric: metric.set
}
}, info)
},
I get the error:
Error: Variable "$_v0_data" got invalid value ["running", "bench
press"] at "_v0_data.metric"; Field "0" is not defined by type
GoalCreatemetricInput. Variable "$_v0_data" got invalid value
["Asdfasdf", "Asdfasdf"] at "_v0_data.metric"; Field "1" is not
defined by type GoalCreatemetricInput.
I don't think you need to use the Json scalar at all. It looks like you're trying to pass an array of strings so instead of [Json!] you may just need to use [String!].
input CreateGoalInput {
title: String!
description: String
metric: [String!]
}
Then you should be able to get rid of
input GoalCreatemetricInput {
set: [Json!]
}
Here you should be able to pass the array of strings to the backend:
mutation CreateGoal ($title: String!, $description: String, $metric: [String!]) {
createGoal(
data: {
title: $title
description: $description
metric: $metric
}
){
id
}
}
And in your resolover I think all you need to do is:
async createGoal(parent, { data }, { request, prisma }, info) {
const { title, description, metric } = data && data
return prisma.mutation.createGoal({
data: {
user: {
connect: {
email: user.email
}
},
title,
description,
metric: { set: metric },
}
}, info)
},

How to use Convector to querie CouchDB Rich Queries with JSON Objects?

I'm here with a problem with rich queries and convector chaincodes, everything works with mango queries, but when I pass content object it's is stringifyed and don't will be sent has an object, but is converted to a string "content":"{\"data\":\"1971\"}", obvious it fails the query
original sample query
{
"selector": {
"type": "io.worldsibu.examples.person",
"attributes": {
"$elemMatch": {
"id": "born-year",
"content": {
"data": "1971"
}
}
}
}
}
graphql query variables
{
"getByAttributeInput": {
"id": "born-year",
"content": {
"data": "1971"
}
},
"skip": 0,
"take": 10
}
packages/person-cc/src/person.controller.ts
chaincode controller method
#Invokable()
public async getByAttribute(
#Param(yup.string())
id: string,
#Param(yup.mixed())
value: any
) {
return await Person.query(Person, {
selector: {
type: c.CONVECTOR_MODEL_PATH_PERSON,
attributes: {
$elemMatch: {
id: id,
content: value
}
}
}
});
}
in docker logs we can view that value is content is sent has a string and not a object ex "content":"{\"data\":\"1971\"}"
{"selector":{"type":"io.worldsibu.examples.person","attributes":{"$elemMatch":{"id":"born-year","content":"{\"data\":\"1971\"}"}}}}
the trick is change #Param(yup.mixed()) to #Param(yup.object()) and now it works has expected, we can query attributes content value with arbitrary and complex objects
#Invokable()
public async getByAttribute(
#Param(yup.string())
id: string,
// #Param(yup.mixed())
#Param(yup.object())
value: any
) {
...

Defining a Mutation argument in graphql-yoga

How do I create a Mutation with arguments for a resolver defined in graphql-yoga as:
const resolvers =
Mutation: {
createProject(root, args) {
const id = (Number(last(data.projects).id) + 1).toString()
const newProject = { ...args, id: id }
...
I've tried the following:
mutation CreateProject($name: String!) {
createProject {
data: {
name: $name
}
}
}
and
mutation CreateProject($name: String!) {
createProject($name: name) {
statusCode
}
}
which produces
and various other structures unsuccessfully.
There seems to be no reference to a Mutation in either the project README or any of the three examples.
Update
I'm now using:
mutation CreateProject($name: String!) {
createProject(name: $name) {
id
name
}
}
which is so similar to examples I've seen on the net that I feel it must be valid & the syntax is not rejected.
The schema definition is:
scalar ID
type Project {
id: ID
type: ProjectType
name: String
}
interface MutationResult {
statusCode: Int
message: String
}
type ProjectMutationResult implements MutationResult {
statusCode: Int
message: String
project: Project
}
type Mutation {
createProject: ProjectMutationResult
}
However on submitting the mutation, I receive:
{
"error": {
"errors": [
{
"message": "Unknown argument \"name\" on field \"createProject\" of type \"Mutation\".",
"locations": [
{
"line": 2,
"column": 17
}
]
},
{
"message": "Cannot query field \"id\" on type \"ProjectMutationResult\".",
"locations": [
{
"line": 3,
"column": 5
}
]
},
{
"message": "Cannot query field \"name\" on type \"ProjectMutationResult\".",
"locations": [
{
"line": 4,
"column": 5
}
]
}
]
}
}
According to your type definition:
The createProject mutation does not expect any argument:
type Mutation {
createProject: ProjectMutationResult
}
The ProjectMutationResult type does not have an id field nor a name field:
type ProjectMutationResult implements MutationResult {
statusCode: Int
message: String
project: Project
}
So when you run the mutation:
mutation CreateProject($name: String!) {
createProject(name: $name) {
id
name
}
}
you have a complete discrepancy between what you're feeding your GraphQL server and what it's actually expecting.
So first of all, if you want to be able to set a name to your project when you create it, you need to amend your createProject definition to this:
type Mutation {
createProject(name: String!): ProjectMutationResult
}
(if you want the naming to be optional, set name to be of type String rather than String!)
Then, assuming you want to retrieve the newly created project id and name from your mutation, change the mutation itself to:
mutation CreateProject($name: String!) {
createProject(name: $name) {
project {
id
name
}
}
}
You need to do this because your createProject mutation returns a ProjectMutationResult which itself contains a project field of type Project, which is the one defining the id and name fields.

Use a GQLObject as arg for a mutation?

I have following mutation on serverside (nodeJS) (RequiredDataType is imported):
mutationA: {
type: MutationResponseType,
args: {
id: {
type: new GraphQLNonNull(GraphQLString)
},
name: {
type: new GraphQLNonNull(GraphQLString)
},
requiredData: {
type: new GraphQLNonNull(new GraphQLList(RequiredDataType))
}
},
async resolve(parentValue, {
id,
name,
requiredData
}, req) {
// Some Magic Code
}
},
The RequiredDataType is coded as follow (All GraphQL things are imported :)):
const RequiredDataType = new GraphQLObjectType({
name: 'RequiredDataType',
fields: {
name: {
type: GraphQLString
},
value: {
type: GraphQLString
},
required: {
type: GraphQLBoolean
}
}
});
module.exports = RequiredDataType;
When I use this code I get the following error: "module initialization error: Error"
If I change the RequiredDataType in the mutation to GraphQLString it works without any error but I can't use the object which I need :)
At the end I will send and process following data structure:
{
"name": "Hallo"
"id": "a54de3d0-a0a6-11e7-bf70-7b64ae72d2b6",
"requiredData": [
{
"name": "givenName",
"value": null,
"required": true
},
{
"name": "familyName",
"value": null,
"required": false
}
]
}
On the client (reactJS with apollo-client) I use the following gql-tag code:
export default gql`
mutation MutationA($id: String!, $name: String!, $requiredData: [RequiredDataType]!){
mutationA(id: $id, name: $name, requiredData: $requiredData) {
id,
somethingElse
}
}
`;
But in the first place it crashes on the mutation declaration on the server. So is it not possible to use and GQLObject as an argument at an mutation or where is my error in the code?
Thank you for your help!
Best,
Fabian
Unfortunately, a type cannot be used in place of an input, and an input cannot be used in place of a type. This is by design. From the official specification:
Fields can define arguments that the client passes up with the query,
to configure their behavior. These inputs can be Strings or Enums, but
they sometimes need to be more complex than this.
The Object type defined above is inappropriate for re‐use here,
because Objects can contain fields that express circular references or
references to interfaces and unions, neither of which is appropriate
for use as an input argument. For this reason, input objects have a
separate type in the system.
You can check this answer for more details as to the why
You'll need to define RequiredDataType as a GraphQLInputObjectType, not a GraphQLObjectType, to get your mutation working. If you need it as a GraphQLObjectType too, you'll need to declare them as two separate types -- something like RequiredDataType and RequiredDataInput.

Resources