Extensions not returned in GraphQL query results - node.js

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.

Related

Implementing retry logic in node-fetch api call

I hope you will forgive the beginners question, I'm trying to implement a simple retry policy for an api call using node-fetch
This is being added to an existing repo using TypeScript so I'm using this also, hence the data definitions.
async checkStatus(custId: string, expectedStatus: string) {
const response = await fetch(
`${'env.API_URL'}/api/customer/applications/${custId}`,
{
method: 'GET',
headers: headers,
},
)
expect(response.status, "Response status should be 200").to.be.equal(200)
const resp = await response.json()
expect(resp.status).to.contain(expectedStatus)
return resp.status;
}
I am calling it like so
await this.checkApplicationStatus(custId, 'NEW')
await this.checkApplicationStatus(custId, 'EXISTING')//and so forth
Is there a neat way of retrying based on an unexpected expectedStatus ?
Again, I appreciate there may be many examples out there but as a beginner, I am struggling to see a good/best-practice approach so looking for someone to provide an example. I don't need to use Chai assertions, this was just my first attempt.
TIA
you can check a library that I've published #teneff/with-retry
to use it you'll have to define error classes like these:
class UnexpectedStatus extends Error {
constructor(readonly status: number) {
super("Unexpected status returned from API");
}
}
class ResourceNotFound extends Error {
constructor() {
super("Resource not found in API");
}
}
and throw respectively depending on the status code:
export const getCustomerApplications = async ( custId, headers ) => {
const result = await fetch(`https://example.com/api/customer/applications/${custId}`, {
method: "GET",
headers,
});
if (result.status === 404) {
throw new ResourceNotFound();
} else if (result.status > 200) {
throw new UnexpectedStatus(result.status);
}
return result;
};
and then just use the HOF from the library to wrap your function, with options (how many retries should be attempted and for which errors should it be retrying)
const retryWhenReourceNotFoundOrUnexpectedStatus = withRetry({
errors: [UnexpectedStatus, ResourceNotFound],
maxCalls: 3,
});
const getCustomerApplicationsWithRetry =
retryWhenReourceNotFoundOrUnexpectedStatus(getCustomerApplications);
const result = await getCustomerApplicationsWithRetry(1234, {
Authorization: "Bearer mockToken",
});
Check this working setup

Pagination in TypeORM, NestJS

I have multiple search condition in my form. if user does not enter anything then all the data should be return. if he gives some search input then only those matching record should be return.
Below code is working fine. the only thing is ,sometimes record are coming around 30-40 with filter condition as well so I have been given requirement to introduce pagination. we have to show 10 record at a time in page with or without filter condition.
Could you please guide me how can I introduce pagination in below code.
async findAll(queryCertificateDto: QueryCertificateDto): Promise<Certificate[]> {
const { certificateNo, requestStatus, protoColNo, noOfSubjects} =queryCertificateDto
const query = this.certificateRepository.createQueryBuilder('certificate');
if (certificateNo) {
query.andWhere('certificate.certificateNo=:certificateNo', { certificateNo });
}
if (requestStatus) {
query.andWhere('certificate.requestStatus=:requestStatus', {
requestStatus,
});
}
if (protoColNo) {
query.andWhere('certificate.protoColNo=:protoColNo', { protoColNo });
}
if (noOfSubjects) {
query.andWhere('certificate.noOfSubjects=:noOfSubjects', { noOfSubjects });
}
const certificates = await query.getMany();
return certificates;
}
export const getAllFaqs = () => async (req: Request, res: Response): Promise<void> => {
const {
query: { userType ,page ,perPage},
} = req;
const faqsPagesRepository = getCustomRepository(FaqsPageRepository);
let where: FindConditions<Faqs> = {};
if (userType) {
where = { ...where, userType };
}
const limit =Number(perPage);
const offset=(Number(page)-1)*limit;
const result = await faqsPagesRepository.findAndCount({
where,
take:limit,
skip:offset,
});
res.status(200).json({ result });
};

Next.js not build when using getStaticPaths and props

I'm trying to run next build when using getStaticProps and getStaticPaths method in one of my routes, but it fails every time. Firstly, it just couldn't connect to my API (which is obvious, they're created using Next.js' API routes which are not available when not running a Next.js app). I thought that maybe running a development server in the background would help. It did, but generated another problems, like these:
Error: Cannot find module for page: /reader/[id]
Error: Cannot find module for page: /
> Build error occurred
Error: Export encountered errors on following paths:
/
/reader/1
Dunno why. Here's the code of /reader/[id]:
const Reader = ({ reader }) => {
const router = useRouter();
return (
<Layout>
<pre>{JSON.stringify(reader, null, 2)}</pre>
</Layout>
);
};
export async function getStaticPaths() {
const response = await fetch("http://localhost:3000/api/readers");
const result: IReader[] = await response.json();
const paths = result.map((result) => ({
params: { id: result.id.toString() },
}));
return {
paths,
fallback: false,
};
}
export async function getStaticProps({ params }) {
const res = await fetch("http://localhost:3000/api/readers/" + params.id);
const result = await res.json();
return { props: { reader: result } };
}
export default Reader;
Nothing special. Code I literally rewritten from the docs and adapted for my site.
And here's the /api/readers/[id] handler.
export default async function handler(
req: NextApiRequest,
res: NextApiResponse
) {
const knex = getKnex();
const { id } = req.query;
switch (req.method) {
case "GET":
try {
const reader = await knex
.select("*")
.from("readers")
.where("id", id)
.first();
res.status(200).json(reader);
} catch {
res.status(500).end();
}
break;
}
}
Nothing special either. So why is it crashing every time I try to build my app? Thanks for any help in advance.
You should not fetch an internal API route from getStaticProps — instead, you can write the fetch code present in API route directly in getStaticProps.
https://nextjs.org/docs/basic-features/data-fetching#write-server-side-code-directly

Sample Apollo Client code to test APQ (Automated Persistent Queries)

I was trying to test APQ with a server written in haskell. The following is the sample Apollo client code, I wrote to test it:
const { createPersistedQueryLink } = require("apollo-link-persisted-queries")
const { createHttpLink } = require("apollo-link-http")
const { InMemoryCache } = require("apollo-cache-inmemory")
const { ApolloClient } = require("apollo-client")
const { gql } = require('apollo-server');
const { ApolloLink } = require("apollo-link")
const fetch = require("node-fetch")
const link = ApolloLink.from([
createPersistedQueryLink(),
createHttpLink({
uri: "http://localhost:8080/v1/graphql",
fetch: fetch,
headers: {
"admin-secret":"password"
}
})
]);
const client = new ApolloClient({
cache: new InMemoryCache(),
link: link
})
async function main() {
const response = await client
.query({
query: gql`
query {
child {
name
}
}`
})
console.log(response.data)
}
main().catch(err => console.log("Err:", err))
But whenever I run this file, I get the following error:
graphQLErrors: [
{
extensions: [Object],
message: "the key 'query' was not present"
}
],
When I check the Request body sent in POST Body, I get the following thing:
{"operationName":null,"variables":{},"extensions":{"persistedQuery":{"version":1,"sha256Hash":"0832c514aef4b1a6d84702e8b2fab452cbb0af61f0a1c4a4c30405e671d40527"}}}
It tells that the query is not sent in the Post Body. Which might be the reason I'm getting the above error.
Hence, I am confused at this point :see_no_evil:
I read through a tons of blogs, but It's not clear as to what HTTP method is used when { useGETForHashedQueries: true } option is not given. From my experiment above, it looks as if - POST method is used.
But if POST method is used, why isn't the query sent in the POST body.
BUT
When I use the { useGETForHashedQueries: true } option, it works correctly. What might I be doing wrong here?
It would be really great, if someone would clear this out for me.

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.

Resources