GraphQL - passing an object of non specific objects as an argument - javascript

I am very new to GraphQL. I'm trying to pass an object like this one as an argument:
{
filters: {
status: 'approved',
id: {
LESS_THAN: 200
}
}
}
Or this object can be like this either;
{
filters: {
status: ['approved', 'pending'],
id: 200
}
}
I know all properties that can be in this object, but all of these properties can be a string/int or an object.
I tried to define it like this but it obviously didn't work:
args: {
filters: { type: new GraphQLNonNull(new GraphQLNonNull(GraphQLString)) },
},
I'm trying to define the argument with a GraphQL type GraphQLInputObjectType.
const OffersFiltersType = new GraphQLInputObjectType({
name: 'Filters',
description: '...',
fields: () => ({})
id: {
type: new GraphQLNonNull({
name: 'Id',
description: '...',
fields: {
}
}),
resolve: (offer) => offer.id
},
}),
});
But how can i specify to this type that my id can be either a int or an object?
This is my Query definition:
const QueryType = new GraphQLObjectType({
name: 'Query',
description: '...',
fields: () => ({
offers: {
type: OffersType,
args: {
limit: { type: GraphQLInt },
page: { type: GraphQLInt },
sort: { type: GraphQLString },
filters: { [HERE] }
},
resolve: (root, args, context, info) => {
const gqlFields = graphqlFields(info);
const fields = Object.keys(gqlFields.offer);
const queryArgs = args;
queryArgs.fields = fields;
return getOffers(queryArgs);
}
},
}),
});
And this is my request with superagent
const getOffers = (args) => {
const queryArgs = args;
if (typeof queryArgs.limit !== 'undefined') {
queryArgs.limit = args.limit;
} else {
queryArgs.limit = Number.MAX_SAFE_INTEGER;
}
return new Promise((fulfill, reject) => {
request
.get(API_URL)
.query(qs.stringify(args))
.end((err, res) => {
if (err) {
reject(err);
}
fulfill(res);
});
});
};
I need this object to construct a query in my resolve function. Thank you all for your help! I only need simple advices!

This is not allowed, by design: https://github.com/graphql/graphql-js/issues/303
GraphQL does not support unknown property names, largely because it would make the schema meaningless. The example given is a simple typo:
If you have the query query ($foo: String) { field(arg: $foo) } and the variables { "fooo": "abc" }, we currently flag this as an error, but we could potentially miss this typo if we did not raise errors.
The schema is meant to ensure compatibility between servers and clients, even across versions, and allowing unknown properties would break that.
There is a merge request open for this in the GraphQL-JS repo, but it is still being debated and has the same problems with typos and general inconsistency.
The idea of returning a primitive or object runs into a similar problem. When accepting an object, you need to list the properties you're expecting and the query will validate those against the schema. The properties, and their types and null-ness, must be known ahead of time for you (and the parser) to build the query and definitely need to be known when you validate.
If you could accept a primitive or object, you would have to specify the fields on that object, but those could not possibly exist on the primitive. That's a problem.

Related

How to modify virtual fields from sequelize findAll result?

I have looked everywhere and couldn't find any clear answers for this.
I have a complex findAll() with many inclusions and each with their own virtual fields.
What I want is to modify the virtual fields of the result, however as it is returning the model instance trying to access the virtual fields returns undefined as they are not in the result yet.
I have tried 'raw: true' but this removes all virtual fields and as my data has nested tables which also have their own virtual fields which I need, I cannot do that.
Example models
var Book = sequelize.define('Book', {
id: {
type: DataTypes.INTEGER,
allowNull: false,
autoIncrement: true,
primaryKey: true
},
title: {
type: DataTypes.STRING
},
author: {
type: DataTypes.STRING
}
//....other columns,
myField: {
type: DataTypes.Virtual,
get() {
return this.getDataValue('title:') + this.getDataValue('author');
})
Getting the data
model.Book.findAll({
limit: 100
})
.then((result) => {
const newBook = result.map(row => {
return {...row, myField: 'setMyOwnValueHere'}
}
return newBook
}
Get model data first : get
model.Book.findAll({
limit: 100
}).then(result => {
const books = result.map(row => {
//this returns all values of the instance,
//also invoking virtual getters
const book = row.get();
book.myField = 'setMyOwnValueHere';
return book;
});
return books;
});

Convert DynamoDb stringset into JSON

This is one of the fields of StringSet type that is returned from DynamoDb.
permissions:
Set {
wrapperName: 'Set',
values:
[ 'BannerConfigReadOnly',
'CampaignBannerCreate',
'CampaignPromoCreate',
'CampaignReadOnly',
'MasterplanReadOnly',
'SegmentCreate',
'SegmentDownload',
'SegmentUpload' ],
type: 'String' }
}
Now, I am using aws.DynamoDB.Converter.unmarshal function to get it in this format
permissions: ['BannerConfigReadOnly',
'CampaignBannerCreate',
'CampaignPromoCreate',
'CampaignReadOnly',
'MasterplanReadOnly',
'SegmentCreate',
'SegmentDownload',
'SegmentUpload']
But, this is what i get
{}
Any ideas what, I may be doing wrong.
This is my code
const aws = require('aws-sdk');
const documentClient = new aws.DynamoDB.DocumentClient();
documentClient.scan(params, (err, data) => {
if (err) {
reject(err);
} else {
let processedItems = [...data.Items];
var test = aws.DynamoDB.Converter.unmarshall(processedItems[0].permissions);
console.log(`test is ${JSON.stringify(test)}`);
}});
ProcessedItems[0] is this
{ email: 'abc#gmail.com',
tenant: 'Canada',
permissions:
Set {
wrapperName: 'Set',
values:
[ 'BannerConfigReadOnly',
'CampaignBannerCreate',
'CampaignPromoCreate',
'CampaignReadOnly',],
type: 'String' } }
That data is already unmarshalled since you are using the DocumentClient. Consider just using processedItems[0].permissions.values to get the values of the set.

In sails use a "basemodel", derive from that and use baseclass functions?

To improve our program and reduce code redundancy, we wish to create some inheritance inside the models..
Now take a typical User model, it has a name and password field as "baseclass" and several subclasses can improve upon this depending in the specific application's needs.
So a baseuser would look like:
module.exports = {
attributes: {
username: {
type: 'string',
required: true,
unique: true
},
password: {
type: 'string',
required: true,
},
},
beforeCreate: async function(user, cb) {
const hash = await bcrypt.hash(user.password, 10);
user.password = hash;
cb();
},
}
This bare class doesn't correspond to any database table in its own. Now in derived class from this, VerifyableUser (a model for users that must have verification links), there are a few extra fields, one which is a verify url.
Now to "extend" classes lodash' _.merge function is used, as explained in this question .
const BaseUser = require("../../BaseUser.js");
module.exports = _.merge({}, BaseUser, {
attributes: {
verify_key: {
type: 'string'
}
},
beforeCreate: async function(user, cb) {
user.verify_key = 'helloworld'; //crypto used to generate...
cb();
}
};
Now the problem should be obvious, the derived class' beforeCreate overwrites the original beforeCreate: in a normal OO environment this isn't a big problem either, as I could just call Base.beforeCreate() or something similar.
However can something be done using lodash' merge? Or should I use another way to extend objects? (Or do I really have to repeat myself and retype the beforeCreate?).
or something similar:
// VerifyableUser
async beforeCreate(user, cb) {
await BaseUser.beforeCreate(user, () => 0);
//...
}
You could also use _.mergeWith to check what each property being merged is and if it is a function just pick the object and not the source (in your case the source is BaseUser):
const BaseUser = require("../../BaseUser.js");
let obj = {
attributes: {
verify_key: {
type: 'string'
}
},
beforeCreate: async function(user, cb) {
user.verify_key = 'helloworld'; //crypto used to generate...
cb();
}
}
module.exports = _.mergeWith(
obj,
BaseUser,
(objValue, srcValue, key, object, source) => _.isFunction(objValue) ? objValue : _.merge(object[key], source[key])
)
Here is a test:
var data = {
food: "chicken",
foo: () => console.log('chicken!')
}
var source = {
prop1: 1,
prop2: 1,
foo: () => console.log('foo!')
}
var result = _.merge(data, source, (objValue, srcValue, key, object, source) => _.isFunction(objValue) ? objValue : _.merge(object[key], source[key]))
console.log(result)
result.foo()
<script src="https://cdnjs.cloudflare.com/ajax/libs/lodash.js/4.17.10/lodash.min.js"></script>

GraphQL.js - use Interface as a default (fallback) type in resolveType function

I'm trying to return generic type in resolveType function if none of the provided types is matched. The example below shows this problem: API worked like a charm supporting UserType and MovieType until in database was added BookType (not supported by GraphQL schema).
const {
graphql,
GraphQLSchema,
GraphQLObjectType,
GraphQLString,
GraphQLNonNull,
GraphQLList,
GraphQLInterfaceType
} = require("graphql");
const DATA = [
{
// UserType
name: "catherine woolf",
nick: "catherine"
},
{
// MovieType
name: "cat woman",
director: "Jack Wolfgang"
},
{
// --- missing type --- (BookType)
name: "cats secrets",
author: "Nicky Glace"
}
];
const resolveType = data => {
if (data.nick) {
return UserType;
}
if (data.director) {
return MovieType;
}
};
const SearchableType = new GraphQLInterfaceType({
name: "Searchable",
fields: {
name: { type: GraphQLString }
},
resolveType: resolveType
});
const UserType = new GraphQLObjectType({
name: "User",
interfaces: [SearchableType],
fields: {
name: { type: GraphQLString },
nick: { type: GraphQLString }
}
});
const MovieType = new GraphQLObjectType({
name: "Movie",
interfaces: [SearchableType],
fields: {
name: { type: GraphQLString },
director: { type: GraphQLString }
}
});
const schema = new GraphQLSchema({
types: [MovieType, UserType, SearchableType],
query: new GraphQLObjectType({
name: "RootQueryType",
fields: {
search: {
type: new GraphQLList(SearchableType),
args: {
text: { type: new GraphQLNonNull(GraphQLString) }
},
resolve(_, { text }) {
return DATA.filter(d => d.name.indexOf(text) !== -1);
}
}
}
})
});
const query = `
{
search(text: "cat") {
name
... on User {
nick
}
... on Movie {
director
}
}
}
`;
graphql(schema, query).then(result => {
console.log(JSON.stringify(result, null, 2));
});
So now this code ends with error:
"Abstract type Searchable must resolve to an Object type at runtime for field RootQueryType.search with value \"[object Object]\", received \"undefined\". Either the Searchable type should provide a \"resolveType\" function or each possible types should provide an \"isTypeOf\" function."
This is nothing surprising since currently resolveType may not return any type.
Workaround
Crate type containing the same field like interface SearchableType (1 to 1 implementation):
const _SearchableType = new GraphQLObjectType({
name: '_Searchable',
interfaces: [SearchableType],
fields: {
name: { type: GraphQLString },
}
});
Use it as a fallback type:
const resolveType = data => {
if (data.nick) {
return UserType;
}
if (data.director) {
return MovieType;
}
return _SearchableType;
};
And add it to types in schema definition:
types: [MovieType, UserType, SearchableType, _SearchableType],
But the problem with this solution is presence of this dummy _SearchableType in documentation like this:
Question
Is there any way to return interface SearchableType or equivalent of it in resolveType? The key point for me is hiding of this "fallback type" in a documentation.
GraphQL is strongly typed and doesn't support generics or some kind of "fallback" mechanism when resolving unions and interfaces. At the end of the day, if your underlying data layer is returning some type that you have not yet implemented in your schema, the simplest solution is to simply add that type to your schema. Migrations to your database and changes to your schema should go hand-in-hand.
If you want to derive your schema from your storage layer, I would suggest looking into something like PostGraphile (formerly PostGraphQL).
That said, if you're bent on using a workaround, you could just fallback to one of the existing types:
const resolveType = data => {
if (data.nick) {
return UserType
}
return MovieType
}
Now a book's name will still be accessible, provided you query it on the interface and not one of the types. The only downside to this approach is that movie-specific fields will be returned for a book and will resolve to null, but that won't cause any issues unless they're specifically defined as non-null in your schema.

Modify GraphQLObjectType fields at runtime

Assume I have the following code as graphql schema. A userType including id and name for users, and there's two kind of queries: allUsers: [userType] and user(id: Int!): userType.
let db = [{
id: 1,
name: 'Amir'
}, {
id: 2,
name: 'John'
}];
const userType = new GraphQLObjectType({
name: 'User',
fields: {
id: { type: GraphQLInt },
name: { type: GraphQLString }
}
});
const queryType = new GraphQLObjectType({
name: 'Query',
fields: {
allUsers: {
type: new GraphQLList(userType),
resolve: () => db
},
user: {
type: userType,
args: {
id: { type: new GraphQLNonNull(GraphQLInt) }
},
resolve: (_, { id }) => db.find(user => user.id == id);
}
}
})
let schema = new GraphQLSchema({ query: queryType });
I need to modify this structure at boot time. I mean before actually executing the last line.
To add more kind of queries, I deferred the schema creation (new GraphQLSchema(...)) to the end, after all the modifications are done. So I can add more fields to the query itself or perhaps modify existing ones.
But how can I modify the types that are already defined? Basically, I need to add other fields to userType, like permissions, which itself is a GraphQLObjectType and has its own resolve function.
Something like this:
let queryFields = {};
const userType = new GraphQLObjectType({
name: 'User',
fields: {
id: { type: GraphQLInt },
name: { type: GraphQLString }
}
});
queryFields['allUsers'] = {
type: new GraphQLList(userType),
// ...
}
queryFields['user'] = {
type: userType,
//...
}
/* HERE <---------------------------------------- */
userType.fields.permission = {
type: GraphQLString,
resolve: user => getPermissionsFor(user);
}
const queryType = new GraphQLObjectType({
name: 'Query',
fields: queryFields
})
var schema = new GraphQLSchema({ query: queryType });
Thanks!
What I have done at the end is to add another layer between the logic of my app and GraphQL. So I have created another library that holds the information about the schema and the types, and it has an API to modify the existing types in the schema. Once all the modifications are in place, we can extract a GraphQL schema from the library.
That's the whole idea. For implementation detail, I have wrote a post here: Distributed Schema Creation in GraphQL

Categories