Feathers services usage and eslint - node.js

In order to add some user data to my rides list, I added this hook:
async (context: HookContext) => {
// eslint-disable-next-line no-restricted-syntax
for (const ride of context.result.data) {
if (typeof ride.driverId !== 'undefined') {
// eslint-disable-next-line no-await-in-loop
const driver = await context.app.service('users').get(ride.driverId);
// We can't copy the complete object because field are not filtered by the internal service.
// #see https://github.com/feathersjs/feathers/issues/1715
ride.driver = {
_id: driver._id,
name: driver.name,
};
}
}
},
ESLint, configured with Airbnb recommendations, throw errors no-restricted-syntax and no-await-in-loop.
I understand why, but I don't know how to make it working without.
What is the correct and working syntax?

The restricted syntax rule doesn’t want you to use for-of.
I’d consider something like a map iterator and see if that doesn’t resolve both issues e g
return context.result.data.map(ride => {
if (typeof ride.driverId !== 'undefined') {
const driver = await context.app.service('users').get(ride.driverId);
// We can't copy the complete object because field are not filtered by the internal service.
// #see https://github.com/feathersjs/feathers/issues/1715
ride.driver = {
_id: driver._id,
name: driver.name,
};
}
}

Related

Multiple REACT API calls vs handling it all in NodeJS

I am currently using AWS DynamoDB for the backend and REACT for the frontend with a NodeJS/AWS Lambda frame.
What I'm trying to do:
Get the information of the teammates in a dataset from a teams table that contains a list of the partition keys of the profiles table, and the name of the team set as the sort key, to get their specific information for that team. I then need to get each of the teammate's universal information that is true across all teams, namely their name and profile picture.
I have 2 options:
Either loop through the list of partition keys in the team dataset that was fetched in REACT, and make a couple of API calls to retrieve their specific information, then a couple more for their universal information.
Or, I was wondering if I can instead then implement the same logic, but instead of the API calls, it will be DynamoDB querying in the Lambda function, then collect them all into a JSON object for it to be fetched by the REACT in one swoop.
Would it be a better idea? Or am I simply doing the REACT logic wrong? I am relatively new to REACT so it's definitely possible and am open to tips.
For reference, here's the code:
useEffect(() => {
let theTeammates : (userProfileResponse | undefined)[] = teammateObjects;
(async () => {
let teammatesPromises : any[] = [];
// teammates = the list of partition keys
for (let i = 0; i < teammates.length; i++) {
if(teammates[i] !== '') {
teammatesPromises.push(getSpecificUser(teammates[i], teamName))
}
}
await Promise.all(teammatesPromises)
.then((resolved) => {
if(resolved) {
theTeammates = resolved.map( (theTeammate) => {
if(theTeammate) {
let user: userProfileResponse = {
userKey: theTeammate.userKey,
teamKey: theTeammate.teamKey,
description: theTeammate.description,
userName: theTeammate.userName,
profilePic: defaultProfile,
isAdmin: theTeammate.isAdmin,
role: theTeammate.role,
}
return user as userProfileResponse;
}
})
}
})
setTeammateObjects(theTeammates as userProfileResponse[]);
}) ();
}, [teamData]);
useEffect(() => {
if(teammateObjects) {
(async () => {
let Teammates : any[] = teammateObjects.filter(x => x !== undefined)
Teammates = Teammates.map( (aTeammate) => {
getProfilePicAndName(aTeammate.userKey).then((profile) => {
if(profile) {
aTeammate.userName = profile.userName;
if(profile.profilePic !== undefined && profile.profilePic !== 'none') {
getProfilePic(profile.profilePic).then((picture) => {
if(picture) {
aTeammate.profilePic = picture
return aTeammate
}
})
} else
return aTeammate
}
})
})
}) ();
}
}, [teammateObjects])
I am currently trying to do it through the react. It works for the most part, but I have noticed that sometimes some of the API calls fail, and some of the teammates don't get fetched successfully and never get displayed to the user until the page is refreshed, which is not acceptable.

ExpressJs: Route with selected parameters only

Is this possible to add the condition directly in the route itself?
Something like this...
...
Router.get('/:status(active|inactive)', index);
...
I know that it can be handled by middleware or in the index method via conditions. However, If this is possible then this way can save a lot of efforts.
Currently I am writing this way:
routes/category.js
Router.get('/:status?', index);
Category/Controller.js
...
const index = async (req, res) => {
try {
const pageParams = paginationParams(req.query.page, configs.perPage);
const conditions = (typeof req.params.status === 'undefined') ? {} : (
['active', 'inactive'].includes(req.params.status) ? req.params.status : {}
);
const count = await Model.count(conditions);
const items = await Model.find(conditions, {
__v: false,
}, {
sort: {
status: -1,
title: 1,
}
})
.skip(pageParams.serialNumber)
.limit(configs.perPage);
...
} catch (error) {
res.status(500).send();
}
};
...
Actually, Router.get('/:status(active|inactive)', index); should work as expected by restricting the possible values for the status parameter to either active or inactive.
You should then be able to retrieve the status parameter as usual in your function with:
const { status } = req.params;
If you don't specify the status parameter as active or inactive on your request URL, the page will 404.
For more, you can read this interesting article on the topic.

Extensions not returned in GraphQL query results

I'm creating an Apollo Client like this:
var { ApolloClient } = require("apollo-boost");
var { InMemoryCache } = require('apollo-cache-inmemory');
var { createHttpLink } = require('apollo-link-http');
var { setContext } = require('apollo-link-context');
exports.createClient = (shop, accessToken) => {
const httpLink = createHttpLink({
uri: `https://${shop}/admin/api/2019-07/graphql.json`,
});
const authLink = setContext((_, { headers }) => {
return {
headers: {
"X-Shopify-Access-Token": accessToken,
"User-Agent": `shopify-app-node 1.0.0 | Shopify App CLI`,
}
}
});
return new ApolloClient({
cache: new InMemoryCache(),
link: authLink.concat(httpLink),
});
};
to hit the Shopify GraphQL API and then running a query like that:
return client.query({
query: gql` {
productVariants(first: 250) {
edges {
node {
price
product {
id
}
}
cursor
}
pageInfo {
hasNextPage
}
}
}
`})
but the returned object only contain data and no extensions which is a problem to figure out the real cost of the query.
Any idea why?
Many thanks for your help
There's a bit of a hacky way to do it that we wrote up before:
You'll need to create a custom apollo link (Apollo’s equivalent of middleware) to intercept the response data as it’s returned from the server, but before it’s inserted into the cache and the components re-rendered.
Here's an example were we pull metrics data from the extensions in our API:
import { ApolloClient, InMemoryCache, HttpLink, ApolloLink } from 'apollo-boost'
const link = new HttpLink({
uri: 'https://serve.onegraph.com/dynamic?show_metrics=true&app_id=<app_id>',
})
const metricsWatchers = {}
let id = 0
export function addMetricsWatcher(f) {
const watcherId = (id++).toString(36)
metricsWatchers[watcherId] = f
return () => {
delete metricsWatchers[watcherId]
}
}
function runWatchers(requestMetrics) {
for (const watcherId of Object.keys(metricsWatchers)) {
try {
metricsWatchers[watcherId](requestMetrics)
} catch (e) {
console.error('error running metrics watcher', e)
}
}
}
// We intercept the response, extract our extensions, mutatively store them,
// then forward the response to the next link
const trackMetrics = new ApolloLink((operation, forward) => {
return forward(operation).map(response => {
runWatchers(
response
? response.extensions
? response.extensions.metrics
: null
: null
)
return response
})
})
function create(initialState) {
return new ApolloClient({
link: trackMetrics.concat(link),
cache: new InMemoryCache().restore(initialState || {}),
})
}
const apolloClient = create(initialState);
Then to use the result in our React components:
import { addMetricsWatcher } from '../integration/apolloClient'
const Page = () => {
const [requestMetrics, updateRequestMetrics] = useState(null)
useEffect(() => {
return addMetricsWatcher(requestMetrics =>
updateRequestMetrics(requestMetrics)
)
})
// Metrics from extensions are available now
return null;
}
Then use a bit of mutable state to track each request and its result, and the use that state to render the metrics inside the app.
Depending on how you're looking to use the extensions data, this may or may not work for you. The implementation is non-deterministic, and can have some slight race conditions between the data that’s rendered and the data that you've extracted from the extensions.
In our case, we store performance metrics data in the extensions - very useful, but ancillary - so we felt the tradeoff was acceptable.
There's also an open issue on the Apollo client repo tracking this feature request
I dont have any idea of ApolloClient but i tried to run your query in shopify graphql app. It return results with extensions. Please find screenshot below. Also You can put questions in ApolloClient github.

Need to find the error with connecting subscription with schema stitching

I am using apollo-server-express for graphql back-end. I am going to process only mutations there, but I want to redirect query and subscription on hasura by means of schema stitching with introspection. Queries through apollo-server to hasura are working fine and returning the expected data.
But subscriptions are not working and I am getting this error: " Expected Iterable, but did not find one for field subscription_root.users".
And besides, server hasura is receiving events:
But apollo-server resents the answer from hasura. It is not the first day I suffer with this and I can not understand what the problem is.
In the editor hasura subscriptions work.
Link to full code
If you need any additional info, I will gladly provide it to you.
import {
introspectSchema,
makeExecutableSchema,
makeRemoteExecutableSchema,
mergeSchemas,
transformSchema,
FilterRootFields
} from 'graphql-tools';
import { HttpLink } from 'apollo-link-http';
import nodeFetch from 'node-fetch';
import { resolvers } from './resolvers';
import { hasRoleResolver } from './directives';
import { typeDefs } from './types';
import { WebSocketLink } from 'apollo-link-ws';
import { split } from 'apollo-link';
import { getMainDefinition } from 'apollo-utilities';
import { SubscriptionClient } from 'subscriptions-transport-ws';
import * as ws from 'ws';
import { OperationTypeNode } from 'graphql';
interface IDefinitionsParams {
operation?: OperationTypeNode,
kind: 'OperationDefinition' | 'FragmentDefinition'
}
const wsurl = 'ws://graphql-engine:8080/v1alpha1/graphql';
const getWsClient = function (wsurl: string) {
const client = new SubscriptionClient(wsurl, {
reconnect: true,
lazy: true
}, ws);
return client;
};
const wsLink = new WebSocketLink(getWsClient(wsurl));
const createRemoteSchema = async () => {
const httpLink = new HttpLink({
uri: 'http://graphql-engine:8080/v1alpha1/graphql',
fetch: (nodeFetch as any)
});
const link = split(
({ query }) => {
const { kind, operation }: IDefinitionsParams = getMainDefinition(query);
console.log('kind = ', kind, 'operation = ', operation);
return kind === 'OperationDefinition' && operation === 'subscription';
},
wsLink,
httpLink,
);
const remoteSchema = await introspectSchema(link);
const remoteExecutableSchema = makeRemoteExecutableSchema({
link,
schema: remoteSchema
});
const renamedSchema = transformSchema(
remoteExecutableSchema,
[
new FilterRootFields((operation, fieldName) => {
return (operation === 'Mutation') ? false : true; // && fieldName === 'password'
})
]
);
return renamedSchema;
};
export const createNewSchema = async () => {
const hasuraExecutableSchema = await createRemoteSchema();
const apolloSchema = makeExecutableSchema({
typeDefs,
resolvers,
directiveResolvers: {
hasRole: hasRoleResolver
}
});
return mergeSchemas({
schemas: [
hasuraExecutableSchema,
apolloSchema
]
});
};
Fixed by installing graphql-tools 4th version. It tutns out the editor did not even notice that I do not have this dependency and simply took the version of node_modules, which was installed by some other package. Problem was with version 3.x. Pull request is where the bug was fixed.
I had the same problem, different cause and solution.
My subscription was working well, until I introduced the 'resolve' key in
my subscription resolver:
Here is the 'Subscription' part of My resolver:
Subscription: {
mySubName: {
resolve: (payload) => {
console.log('In mySubName resolver, payload:',payload)
return payload;
},
subscribe:() => pubSub.asyncIterator(['requestsIncomplete']),
// )
},
The console.log proved the resolve() function was being called with a well structured payload (shaped the same as my Schema definiton - specifically the an object with a key named after the graphQL Subscriber, pointing to an array (array is an iterable):
In mySubName resolver, payload: { mySubName:
[ { id: 41,
...,
},
{...},
{...}
...
...
]
Even though I was returning that same unadulterated object, it caused the error expected Iterable, but did not find one for field "Subscription.mySubName"
When I commented out that resolve function all together, the subscription worked, which is further evidence that my payload was well structured, with the right key pointing to an iterable.
I must be mis-using the resolve field. From https://www.apollographql.com/docs/graphql-subscriptions/subscriptions-to-schema/
When using subscribe field, it's also possible to manipulate the event
payload before running it through the GraphQL execution engine.
Add resolve method near your subscribe and change the payload as you wish
so I am not sure how to properly use that function, specifically don't know what shape object to return from it, but using it as above breaks the subscription in the same manner you describe in your question.
I was already using graphql-tools 4.0.0, I upgraded to 4.0.8 but it made no difference.

sinon.spy in my Node.JS project when testing an AWS service not working as expected

in my Node.JS project (a backend for an Angular 5 project) I have created a service that deals with the AWS Authentication... I have called this awsAuthenticationService. All works well but I now need to test it. In my awsAuthenticationService.js I have the following method that has some minor logic and then calls a method provided by the "cognitoIdentityServiceProvider". Here is a snippet of my code (I really have reduced this)
constructor() {
this._cognitoIdentityServiceProvider = new AWS.CognitoIdentityServiceProvider(this.cognitoConfig);
}
toggleUserAccess(userName, type) {
const params = {
Username: userName,
UserPoolId: this.cognitoConfig.userPoolId
};
if (type === null) {
return this._cognitoIdentityServiceProvider.adminEnableUser(params).promise();
}
return this._cognitoIdentityServiceProvider.adminDisableUser(params).promise();
}
As you can see from the toggleUserAccess we pass a few parameters, determine what they are then call the appropriate method. I wish to test this by having a unit test that will call the authenticationService.toggleUserAccess, pass some params and spy on the authenticationService._cognitoIdentityServiceProvider methods to see if they were called. I set it up so...
let authenticationService = require('./awsAuthenticationService');
describe('toggleUserAccess', () => {
beforeEach(() => {
authenticationService._cognitoIdentityServiceProvider = {
adminDisableUser(params) {
return {
promise() {
return Promise.resolve(params);
}
};
}
};
authenticationService._cognitoIdentityServiceProvider = {
adminEnableUser(params) {
return {
promise() {
return Promise.resolve(params);
}
};
}
};
});
it('should call adminEnableUser if the type is null', () => {
authenticationService.toggleUserAccess('TheUser', null);
const spyCognito = sinon.spy(authenticationService._cognitoIdentityServiceProvider, 'adminEnableUser');
expect(spyCognito.calledOnce).to.equal(true);
});
it('should call adminDisableUser if the type is null', () => {
authenticationService.toggleUserAccess('TheUser', '0001');
const spyCognito = sinon.spy(authenticationService._cognitoIdentityServiceProvider, 'adminDisableUser');
expect(spyCognito.calledOnce).to.equal(true);
});
});
My tests aren't passing and I think I have set up my sinon.spys incorrectly - can anyone see what I am doing wrong or give advice please
To stub class of AWS.CognitoIdentityServiceProvider, need to stub with its prototype keyword.
// add require statement for your AWS class
const spyCognito = sinon.spy(AWS.CognitoIdentityServiceProvider.prototype, 'adminDisableUser');
expect(spyCognito.calledOnce).to.equal(true);
Hope it helps

Resources