graphql , understanding _ in resolve function in new GraphQLObjectType - graphql-js

what does mean '_' parameter in resolve function ?. what is it for ?
const queryType = new GraphQLObjectType({
name: 'RootQuery',
fields: {
usersCount: {
type: GraphQLInt,
resolve: (_, args, { db }) =>
db.collection('users').count()
}
}
});
where i can read documentation about resolve parameters?

"_" it's just to assign anything, because this parameter will not be used.
resolve: (root, args, context, info) => ...
Read more on:
https://github.com/graphql/graphql-js/issues/844

Related

Redux toolkit query. useLazyQuery

Try to understand how to structure queries.
What I have now:
File for CRUD:
export const PromoService = apiClient.injectEndpoints({
endpoints: (build) => ({
fetchPromoById: build.query<
Promotion,
{ ppeType: PpeType; id: string }
>({
query: ({ ppeType, id }) => apiQuery(ppeType, 'fetchPromoById', id),
providesTags: (_result, _err) => [{ type: 'Promo' }],
}),
fetchPromoByCategory: build.mutation<
PromotionData,
{ ppeType: PpeType; type: string; bannerId: string }
>({
query: ({ ppeType, type, bannerId }) => ({
url: apiQuery(ppeType, 'fetchPromoByCategory'),
method: 'POST',
body: fetchPromoByCategoryBody(type, bannerId),
}),
invalidatesTags: ['Promo'],
}),
}),
});
export const { useLazyFetchPromoByIdQuery, useFetchPromoByCategoryMutation } =
PromoService;
File for slices:
const initialState: PromotionState = {
chosenPromotion: {} as Promotion,
promoList: [],
};
const promoSlice = createSlice({
name: 'promo',
initialState,
reducers: {
setChosenPromotion: (state, action: PayloadAction<Promotion>) => {
state.chosenPromotion = action.payload;
},
setPromoList: (state, action: PayloadAction<Promotion[]>) => {
state.promoList = action.payload;
},
},
});
Component:
const [fetchPromoByCategory, { isLoading, data: categoryData }] =
useFetchPromoByCategoryMutation({
fixedCacheKey: 'shared-update-promo',
});
const [trigger, result] = useLazyFetchPromoByIdQuery();
const chosenPromo = result.data;
useEffect(() => {
chosenPromo && dispatch(setChosenPromotion(chosenPromo));
}, [chosenPromo]);
There is no problem get data from useMutation in different components skipping the stage of store data via reducer.
Just use fixedCacheKey and it works fine.
Is it possible to use similar approach for getting data in different components with useLazyQuery?
I use additional dispatch to store data from useLazyQuery but I'm sure it's not appropriate approach.
It is perfectly valid to have multiple different query cache entries at once, so useLazyQuery will not initialize to one of them - it will get it's arguments once you call the trigger function.
It looks like you should use useQuery here, sometimes with the skip parameter when you don't want anything fetched from the start.

How to implement a node query resolver with apollo / graphql

I am working on implementing a node interface for graphql -- a pretty standard design pattern.
Looking for guidance on the best way to implement a node query resolver for graphql
node(id ID!): Node
The main thing that I am struggling with is how to encode/decode the ID the typename so that we can find the right table/collection to query from.
Currently I am using postgreSQL uuid strategy with pgcrytpo to generate ids.
Where is the right seam in the application to do this?:
could be done in the primary key generation at the database
could be done at the graphql seam (using a visitor pattern maybe)
And once the best seam is picked:
how/where do you encode/decode?
Note my stack is:
ApolloClient/Server (from graphql-yoga)
node
TypeORM
PostgreSQL
The id exposed to the client (the global object id) is not persisted on the backend -- the encoding and decoding should be done by the GraphQL server itself. Here's a rough example based on how relay does it:
import Foo from '../../models/Foo'
function encode (id, __typename) {
return Buffer.from(`${id}:${__typename}`, 'utf8').toString('base64');
}
function decode (objectId) {
const decoded = Buffer.from(objectId, 'base64').toString('utf8')
const parts = decoded.split(':')
return {
id: parts[0],
__typename: parts[1],
}
}
const typeDefs = `
type Query {
node(id: ID!): Node
}
type Foo implements Node {
id: ID!
foo: String
}
interface Node {
id: ID!
}
`;
// Just in case model name and typename do not always match
const modelsByTypename = {
Foo,
}
const resolvers = {
Query: {
node: async (root, args, context) => {
const { __typename, id } = decode(args.id)
const Model = modelsByTypename[__typename]
const node = await Model.getById(id)
return {
...node,
__typename,
};
},
},
Foo: {
id: (obj) => encode(obj.id, 'Foo')
}
};
Note: by returning the __typename, we're letting GraphQL's default resolveType behavior figure out which type the interface is returning, so there's no need to provide a resolver for __resolveType.
Edit: to apply the id logic to multiple types:
function addIDResolvers (resolvers, types) {
for (const type of types) {
if (!resolvers[type]) {
resolvers[type] = {}
}
resolvers[type].id = encode(obj.id, type)
}
}
addIDResolvers(resolvers, ['Foo', 'Bar', 'Qux'])
#Jonathan I can share an implementation that I have and you see what you think. This is using graphql-js, MongoDB and relay on the client.
/**
* Given a function to map from an ID to an underlying object, and a function
* to map from an underlying object to the concrete GraphQLObjectType it
* corresponds to, constructs a `Node` interface that objects can implement,
* and a field config for a `node` root field.
*
* If the typeResolver is omitted, object resolution on the interface will be
* handled with the `isTypeOf` method on object types, as with any GraphQL
* interface without a provided `resolveType` method.
*/
export function nodeDefinitions<TContext>(
idFetcher: (id: string, context: TContext, info: GraphQLResolveInfo) => any,
typeResolver?: ?GraphQLTypeResolver<*, TContext>,
): GraphQLNodeDefinitions<TContext> {
const nodeInterface = new GraphQLInterfaceType({
name: 'Node',
description: 'An object with an ID',
fields: () => ({
id: {
type: new GraphQLNonNull(GraphQLID),
description: 'The id of the object.',
},
}),
resolveType: typeResolver,
});
const nodeField = {
name: 'node',
description: 'Fetches an object given its ID',
type: nodeInterface,
args: {
id: {
type: GraphQLID,
description: 'The ID of an object',
},
},
resolve: (obj, { id }, context, info) => (id ? idFetcher(id, context, info) : null),
};
const nodesField = {
name: 'nodes',
description: 'Fetches objects given their IDs',
type: new GraphQLNonNull(new GraphQLList(nodeInterface)),
args: {
ids: {
type: new GraphQLNonNull(new GraphQLList(new GraphQLNonNull(GraphQLID))),
description: 'The IDs of objects',
},
},
resolve: (obj, { ids }, context, info) => Promise.all(ids.map(id => Promise.resolve(idFetcher(id, context, info)))),
};
return { nodeInterface, nodeField, nodesField };
}
Then:
import { nodeDefinitions } from './node';
const { nodeField, nodesField, nodeInterface } = nodeDefinitions(
// A method that maps from a global id to an object
async (globalId, context) => {
const { id, type } = fromGlobalId(globalId);
if (type === 'User') {
return UserLoader.load(context, id);
}
....
...
...
// it should not get here
return null;
},
// A method that maps from an object to a type
obj => {
if (obj instanceof User) {
return UserType;
}
....
....
// it should not get here
return null;
},
);
The load method resolves the actual object. This part you would have work more specifically with your DB and etc...
If it's not clear, you can ask! Hope it helps :)

Custom Validator Angular 2

I've written a web api function that takes a username from the textfield and checks if the username is already taken. To know if the username is available or not, my server returns Y if it is available and N if its not.
To validate the username, I'm using a ValidatorFn in Angular2 so validate the input. However, my validator function is not working.
Here is the validator function:
interface Validator<T extends FormControl> {
(c: T): { [error: string]: any };
}
function validateUsername(c: string) : ValidatorFn {
return (this.isAvailable(c)=='Y') ? null : {
validateUsername: {
valid: false
}
};
}
Here is the isAvailable function:
private isAvailable(username: string) {
let usernameAvailable;
let url = 'URL/api/auth/checkuser/' + username;
let headers = new Headers();
headers.append('User', sessionStorage.getItem('username'));
headers.append('Token', sessionStorage.getItem('token'));
headers.append('AccessTime', sessionStorage.getItem('AccessTime'));
let options = new RequestOptions({ headers: headers });
this.http.get(url, options)
.subscribe((res: Response) => usernameAvailable);
return usernameAvailable; //returns Y or N
}
Form Builder:
complexForm: FormGroup;
constructor(private http: Http, fb: FormBuilder) {
this.complexForm = fb.group({
'username': [null, Validators.compose([Validators.required, Validators.minLength(5), Validators.maxLength(10), validateUsername(this.complexForm.controls['username'].value)])],
})
}
validateUsername(this.complexForm.controls['username'].value) is failing and I'm getting this error:
[ts] Type '{ validateUsername: { valid: boolean; }; }' is not assignable to type 'ValidatorFn'. Object literal may only specify known properties, and 'validateUsername' does not exist in type 'ValidatorFn'. (property) validateUsername: {
valid: boolean;
}
You not adding your validator function correctly. You don't need to call your function when you register it:
this.complexForm = fb.group({
'username': [null, Validators.compose(
[
Validators.required,
Validators.minLength(5),
Validators.maxLength(10),
validateUsername <----- don't call it here
]
)],
})
You can see that some functions are called:
Validators.minLength(5),
But that is factory function call and not a validator function call. During initialization they return ValidatorFn:
/**
* Validator that requires controls to have a value of a minimum length.
*/
static minLength(minLength: number): ValidatorFn {
return (control: AbstractControl): ValidationErrors | null => {
...
}
See more in the official docs.
Also, it seems that your validator is async, so you have to pass it in the async array. And I don't think you need Validators.compose. The correct configuration should therefore be like this:
this.complexForm = fb.group({
'username': [null, [
Validators.required,
Validators.minLength(5),
Validators.maxLength(10),
], [validateUsername]]
})
Regarding the error:
Type '{ valid: boolean; }' is not assignable to type ValidatorFn.
You need to use the correct return type ValidationErrors instead of ValidatorFn:
function validateUsername(c: string) : ValidationErrors {
return (this.isAvailable(c)=='Y') ? null : {
validateUsername: {
valid: false
}
};
}

Argument of type '(snap: DataSnapshot) => void' is not assignable to parameter of type '(a: DataSnapshot) => boolean'

I've already read several questions and answers about this problem but wasn't able to solve it.
I'm using Ionic2 and I have a method which retrieves data from Firebase Database v3.
I don't understand why I get following error in console when I do ionic serve:
Error TS2345: Argument of type '(snap: DataSnapshot) => void' is not assignable to parameter of type '(a: DataSnapshot) => boolean'.
Type 'void' is not assignable to type 'boolean'.
This is the method:
constructor(private http: Http) {
firebase.database().ref('users').orderByChild("id").on("value", function(snapshot){
let items = [];
snapshot.forEach(snap => {
items.push({
uid: snap.val().uid,
username: snap.val().username,
});
});
});
}
}
The forEach method in the DataSnapshot has this signature:
forEach(action: (a: firebase.database.DataSnapshot) => boolean): boolean;
as the action can return true to short-circuit the enumeration and return early. If a falsy value is returned, enumeration continues normally. (This is mentioned in the documentation.)
To appease the TypeScript compiler, the simplest solution would be to return false (to continue enumerating the child snapshots):
database()
.ref("users")
.orderByChild("id")
.on("value", (snapshot) => {
let items = [];
snapshot.forEach((snap) => {
items.push({
uid: snap.val().uid,
username: snap.val().username
});
return false;
});
});
For Typescript version I came out with this solution:
db
.ref(`jobs`)
.orderByChild("counter")
.on("value", (querySnapshot) => {
const jobs: any[] = [];
querySnapshot.forEach((jobRef) => {
jobs.push(jobRef.val());
});
jobs.forEach(async (job) => {
await minuteRT(job);
});
res.status(200).send("done!");
});
In my case I had to return true, to cancel the enumeration:
// You can cancel the enumeration at any point by having your callback
// function return true. For example, the following code sample will only
// fire the callback function one time:
var query = firebase.database().ref("users").orderByKey();
query.once("value")
.then(function(snapshot) {
snapshot.forEach(function(childSnapshot) {
var key = childSnapshot.key; // "ada"
// Cancel enumeration
return true;
});
});
Documentation: [https://firebase.google.com/docs/reference/js/v8/firebase.database.DataSnapshot#foreach][1]

GraphQL, Relay and Mongodb (mongoose) how to fetch array

I can't fetch array or object with mongoose to use it in Relay connection, actually this works fine:
let queryType = new GraphQLObjectType({
name: 'Query',
fields: () => ({
getUsers: {
type: new GraphQLList(userType),
resolve: (_, args) => {
return new Promise((resolve, reject) => {
Users.find(args, (err, users) => {
err ? reject(err) : resolve(users);
});
});
}
}
})
});
in GraphiQL i can get array. But when I try use the same "resolve" function with Relay it doesn't work!
getUsers: {
node: nodeField,
type: usersType,
resolve: (_, args) => {
return new Promise((resolve, reject) => {
Users.find(args, (err, users) => {
err ? reject(err) : resolve(users);
});
});
}
}
and I think reason is because Users.find() return Promise instead of Array or Obj.
Same function if I just change "Users.find()" to any other function with return array works fine.
So main question how can I get array or object with "Users.find()" instead of Promise? (,lean(), .exec()) doesn't help too.
npm i graphql-relay-connection --save
const {connectionFromPromisedArray} = mongooseConnection;
and instead of connectionFromArray(get(), args) we can pass Promise: connectionFromPromisedArray(collection.find(), args)
Finally you can fetch array from Promise without tons of code.