How to resolve two fields in one #ResolveField method - graphql

I have orders resolver, like this one(thats an example, not actually problem):
#Resolver(() => OrderEntity)
export class OrderResolver {
constructor(
private readonly orderService: OrderService,
private readonly usersService: UsersService,
) {}
.................
#ResolveField('users', () => [UsersEntity])
async users(#Parent() order: OrderEntity): Promise<UserEntity[]> {
return await this.usersService.findAllByOrderId(order.id);
}
#ResolveField('usersCount', () => Int)
async usersCount(#Parent() order: OrderEntity): Promise<number> {
const users = await this.usersService.findAllByOrderId(order.id);
return users.lenght; // i can't use like this: order.users.lenght, couse it's still undefined
}
}
And there are i call userService.findAllByOrderId method two times, because i can't use order.users from context in this method, as its still undefined
So how can i write one #ResolveField method for both fields: order.users and order.usersCount, or how to call usersCount method when order.users are already existing?
Thank's a lot for any answers!
I call one method 2 times, instead 1, how can i optimize it?

Do you really need to do a query to get the number of the noOfUsers ?. Cant you just call the async users function that returns UserEntity[] and then get the length of the array that is returned .length
or
If you really need to return the noOfUsers as an int then you can create a wrapper object and put the 2 fields in that e.g.
export class UserResponse {
users: Array<UserEntity>;
noOfUsers: int
}
Then you can only have 1 method that returns both fields:
#ResolveField('users', () => [UserResponse])
async users(#Parent() order: OrderEntity): Promise<UserResponse> {
const userEntities: Array<UserEntity> = await this.usersService.findAllByOrderId(order.id);
const response: UserResponse = {
users: userEntities,
noOfUsers: userEntities.length
}
return response;
}

Related

Angular how to combine local function return value with runtime call back http request

I have local function to check some validation which returns true/false. I also have runtime callback function which is an async function ie. http call.
Note: This checkPermission function is happening inside a for loop.
I want to check if any othese two function call is true. Can anyone help me how to achieve this?
private checkPermissions(
moduleId: number,
permissions: number[],
callback?: () => Observable<boolean>
): boolean {
if(callback) {
console.log('callback function defined');
}
//following is the local function. how to make callback() here?
return this.userSecurityService.userHasLicenseAndPermission(
moduleId,
permissions
);
}
My complete code is:
Component:
options: NavOption[] = [];
this.options = this.sideNavService.loadMenus();
Sidenav service:
loadMenus(): NavOption[] {
return this.getMenus();
}
private getMenus(): NavOption[] {
const filteredMenuItems: NavOption[] = [];
let menus = [{
id: 'recorded-events',
label: 'Recorded events',
icon: 'far fa-calendar-check fa-2x',
url: `/incident/${this.organisationId}/list`,
permissions: [
EventReportingPermissions.View,
EventReportingPermissions.ViewOwnEvents,
EventReportingPermissions.ViewEmployeesEvents
],
additionalPermissionCheck: () =>
this.eventAccessGroupService.hasEventAccessGroupException()//this is the service to make http call
},
{
id: 'new-events',
label: 'Report new event',
icon: 'far fa-calendar-plus fa-2x',
url: `/incident/${this.organisationId}/create`,
permissions: [EventReportingPermissions.Report]
}]
for(let item of menus) {
let canAccess = this.checkPermissions(
topLevelItem.module,
subItem.permissions
);
filteredMenuItems.push(item);
}
return filteredMenuItems;
}
//local function
private checkPermissions(moduleId: number, permissions: number[]): boolean {
//following returns value from local function and no http call
return this.userSecurityService.userHasLicenseAndPermission(
moduleId,
permissions
);
}
//additionalPermissionCheck?: () => Observable<boolean>;
I am not sure I am understanding correctly but is your callback the function that performs the permission checking?
If so you can use a map pipe:
// Beware this returns Observable<boolean> and not boolean
const safeCallbackResult = callback ? callback() : of(true) // default to returning true as we'd like to check for the second condition
return callback().pipe(
map(canDoAction => canDoAction ? this.userSecurityService.userHasLicenseAndPermission(...) : false)
)
If you'd like to return a boolean, you can't. Because the moment you need to await for the callback's observable emission that is an operation that can take some time. Even though you could make the function async
private async checkPermissions(
moduleId: number,
permissions: number[],
callback?: () => Observable<boolean>
): Promise<boolean> {
// callback().toPromise() if using RxJS 6
// firstValueFrom(callback()) if using RxJS 7
if(callback && ! (await callback().toPromise())) return false
return this.userSecurityService.userHasLicenseAndPermission(...)
}
Something like this:
sub = myHttpGetCall$().subscribe(value => {
if (value && localValue) {
// do whatever when both are true
}
}
Where localValue is the return value from your local function, which I assume is not an async operation.
Use an RxJs iif https://www.learnrxjs.io/learn-rxjs/operators/conditional/iif
booleanObservable$ = iif(() => yourLocalCondition, yourHttpRequest$, of(false));
If your localCondition is true it will make the http request otherwise there is no point so it just retuns an observable that emits false.

How to organize GraphQL resolver for additional fields

Let's say I have a simple GraphQL type for a user:
type User {
id: ID!
name: String!
}
Query {
user(id:ID!)
}
and a resolver
user = (_, {id}, {api})=> api.getUser(id)
Now I have add a new field to the User called friends and added a new resolver for the User.friends field.
friends = ({id}, _, {api})=> api.getFriends(id)
So now I wonder when we made a query like this, how can I prevent the call to api.getUser but only call api.getFriends.
query {
user(id){
friends {
name
}
}
}
My understanding is that having a resolver defined for the user field in the Query type, it will always call this resolver first and after that all resolvers for fields in the User type.
This is a common problem and there is for example this solution out there: https://github.com/gajus/graphql-lazyloader
Check out the README of the project for a structured description of your problem.
Alternatively, you can implement your own class that contains a cached value making use of how GraphQL.js implements default resolvers:
class User {
constructor(id) {
this.id = id;
}
getInstance({ api }) {
if (!this.instance) {
this.instance = api.getUser(this.id);
}
return this.instance;
}
// notice how id is already a property of this class
name(args, ctx) {
return this.getInstance(ctx).then(instance => instance.name);
}
// do the same for other fields, user will only be fetched once.
friends(args, { api }) {
return api.getFriends(this.id);
}
}
const resolvers = {
Query: {
user: (args) => new User(args.id),
}
}
If you use dataloader you can even do this with even less code thanks to caching in dataloader:
// You probably have this function already somewhere in your apollo server creation
function createContext({ api }) {
return {
api,
loaders: {
user: new Dataloader((ids) => ids.map(id => api.getUser(id))),
},
}
}
const resolvers = {
Query: {
user: (parent, args) => ({ id: args.id }),
},
User: {
name: ({ id }, args, { loaders }) =>
loaders.user.load(id).then(user => user.name),
otherProp: ({ id }, args, { loaders }) =>
loaders.user.load(id).then(user => user.otherProp),
friends: ({ id }, args, { api })=> api.getFriends(id),
}
}
Dataloader will, even when called twice, only reach to the API once. An added benefit is, that it will cache the value. Ideally, you even provide a batch load function in the API to make the loader even more efficient.
Be aware, that user.fields.name now makes calls for every friend to the API. To avoid that, you could check if the property exists:
name: (parent, args, { loaders }) =>
parent.name ?? loaders.user.load(parent.id).then(user => user.name),

Use an object property as a value with a different name in GraphQl

Internally in my server my entities are handled using the database's native fields where possible, so the entity's type is keyed with "dgraph.type". My graphql api does not need to know that the database is dgraph, but I don't want to have to change the field name on every resolver. Is it possible to create a Scalar or some other process so that I can send
{
"dgraph.type": "User",
uid: "0x01",
username: "JimNaysium",
}
and have the client receive
{
type: "User",
uid: "0x01",
username: "JimNaysium",
}
If you've found your way to this benighted question, the answer is: There is no built in way to do this. Apollo server's plugins all trigger too early or too late to help and the schema cannot rename properties. I solved this using the following code:
type KeyedList<T = any> = {[key: string]: any};
const getConvertTypesResolver = (
resolver: (parent: any, args: any, context: any, info: any) => Promise<any>
): any => {
return async (parent: any, args: any, context: any, info: any): Promise<any> => {
const result = await resolver(parent, args, context, info);
// Check for serializable data now so the processor does not choke on
// circular references later.
try {
JSON.stringify(result);
} catch (e) {
throw new Error("Resolver results must be serializable.");
}
const nestedKeysArrays: string[][] = [Object.keys(result)];
const path: KeyedList[] = [result];
// Iterate over every nested object in the result body.
while (nestedKeysArrays.length) {
let done = true;
// Iterate over every key of every object.
while (nestedKeysArrays[0].length) {
// Progressively destroy the keys arrays to prevent rework.
const key = nestedKeysArrays[0].shift();
if (!key) {
continue;
}
const current = path[0][key];
// If the current key is an object add it to the beginning of the
// lists and begin processing it now.
if (current && typeof current === "object") {
nestedKeysArrays.unshift(Object.keys(current));
path.unshift(current);
done = false;
break;
}
// Change the "dgraph.type" key to "type". This is where the real
// work is done. Everything else is just navigation.
if (key === "dgraph.type") {
path[0].type = path[0]["dgraph.type"];
delete path[0]["dgraph.type"];
}
}
if (done) {
// Remove the array of keys from the list of keys to be processed.
nestedKeysArrays.shift();
// Return to the previous object.
path.shift();
}
}
return result;
};
};
The function middlewares a resolver. I'm calling it before constructing ApolloServer on every resolver in the project, and using it's result in lieu of the original resolver.
The issue can be more succinctly solved if you don't avoid recursion, but then you would be using recursion. Good luck in your future endeavors.

Is it possible to add another field in the final response of GraphQL query?

I've been trying to research on how to add another root property of a GraphQL response but found nothing after 1 hour.
Normally, a GraphQL query looks like this:
{
myQuery() {
name
}
}
It responds with:
{
"data": {
"myQuery": []
}
}
I'm curious if I can add another root property in this response say "meta"
{
"data": {
"myQuery": []
},
"meta": {
"page": 1,
"count": 10,
"totalItems": 90
}
}
Is this possible, if not what's the best approach in tackling this with respect to GraphQL?
Thanks!
The apollo-server middleware can be configured with a number of configuration options, including a formatResponse function that allows you to modify the outgoing GraphQL response
const formatResponse = (response) => {
return {
meta
...response
}
}
app.use('/graphql', bodyParser.json(), graphqlExpress({
schema,
formatResponse,
}));
You could pass the req object down to your context, mutate it within your resolver(s) and then use the result inside formatResponse. Something like...
app.use('/graphql', bodyParser.json(), (req, res, next) => graphqlExpress({
schema,
formatResponse: (gqlResponse) => ({
...gqlResponse
meta: req.metadata
}),
})(req, res, next));
Typically, though, you would want to include the metadata as part of your actual schema and have it included with the data. That will also allow you to potentially request multiple queries and get the metadata for all of them.
There's any number of ways to do that, depending on how your data is structured, but here's an example:
type Query {
getFoos: QueryResponse
getBars: QueryResponse
}
type QueryResponse {
results: [Result]
meta: MetaData
}
union Result = Bar | Foo
You can add anything in the response as well... Please follow below code.
app.use('/graphql', bodyParser.json(), graphqlExpress(req => {
return {
schema: tpSchemaNew,
context: {
dbModel
},
formatError: err => {
if (err.originalError && err.originalError.error_message) {
err.message = err.originalError.error_message;
}
return err;
},
formatResponse : res => {
res['meta'] = 'Hey';
return res;
}
}
}))
Apollo Server-specific:
Just adding to the previous answers that formatResponse() has another useful argument, requestContext.
If you are interested in extracting values from that (for example, the context passed to the resolver), you can do the following. BEWARE HOWEVER, the context will likely contain sensitive data that is supposed to be private. You may be leaking authentication data and secrets if not careful.
const server = new ApolloServer({
schema,
formatResponse: (response, requestContext) => {
//return response
const userId = requestContext.context.user.id
response = Object.assign(response, {
extensions: {
meta: {
userId: userId
}
}
}
return response
},
})
The above will return something like this in the gql query response (note the extensions object):
{
data: {
user: {
firstName: 'Hello',
lastName: 'World'
}
},
extensions: { // <= in Typescript, there is no `meta` in GraphQLResponse, but you can use extensions
meta: {
userId: 1234 //<= data from the context
}
}
}
The full list of properties available in requestContext:
at node_modules/apollo-server-types/src/index.ts>GraphQLRequestContext
export interface GraphQLRequestContext<TContext = Record<string, any>> {
readonly request: GraphQLRequest;
readonly response?: GraphQLResponse;
readonly context: TContext;
readonly cache: KeyValueCache;
// This will be replaced with the `operationID`.
readonly queryHash?: string;
readonly document?: DocumentNode;
readonly source?: string;
// `operationName` is set based on the operation AST, so it is defined even if
// no `request.operationName` was passed in. It will be set to `null` for an
// anonymous operation, or if `requestName.operationName` was passed in but
// doesn't resolve to an operation in the document.
readonly operationName?: string | null;
readonly operation?: OperationDefinitionNode;
/**
* Unformatted errors which have occurred during the request. Note that these
* are present earlier in the request pipeline and differ from **formatted**
* errors which are the result of running the user-configurable `formatError`
* transformation function over specific errors.
*/
readonly errors?: ReadonlyArray<GraphQLError>;
readonly metrics?: GraphQLRequestMetrics;
debug?: boolean;
}

Dispatch Action with Observable Value

I often find myself using the following code:
export class Component implements OnDestroy {
private subscription: Subscription;
user: string;
constructor(private store: UserStore) {
this.subscription = store.select(fromUsers.getUser)
.subscribe(user => this.user = user);
}
ngOnDestroy(): void {
this.subscription.unsubscribe();
}
logout(): void {
this.store.dispatch({
type: LOGOUT,
payload: {
user: this.user
}
})
}
}
As you can see I need to store the user string as a member within the component to send it with my payload.
I would rather use the user string as an observable and make use of the async pipe.
How do I need to change my code to leverage the observable of the user when dispatching the action without storing it in a member variable?
You can use ngrx effects and enhance the LOGOUT command with current user.
#Effect() logoutEffect$ = this.actions$
.ofType(LOGOUT)
.withLatestFrom(this.store$)
.map(([action: Action, storeState: AppState]) => {
return storeState.getUser;
})
.map(payload => ({type: 'LOGOUT_USER', payload}))

Resources