Closed johnf closed 4 months ago
This is the patch I'm using
diff --git a/node_modules/@aws-amplify/api-graphql/src/internals/generateClient.ts b/node_modules/@aws-amplify/api-graphql/src/internals/generateClient.ts
index 7bf63b9..b0dc4e3 100644
--- a/node_modules/@aws-amplify/api-graphql/src/internals/generateClient.ts
+++ b/node_modules/@aws-amplify/api-graphql/src/internals/generateClient.ts
@@ -28,12 +28,13 @@ export function generateClient<T extends Record<any, any> = never>(
[__authMode]: params.authMode,
[__authToken]: params.authToken,
[__headers]: params.headers,
- graphql,
cancel,
isCancelError,
models: {},
} as any;
+ client.graphql = graphql.bind(client);
+
client.models = generateModelsProperty<T>(client, params);
return client as V6Client<T>;
@johnf - Could you share code snippets demonstrating where / how you are calling both Amplify.configure
and generateClient
? Thank you!
Sure.
NOTE: For completeness I've added my retry wrapper below, but I had the same issue without the wrapper
// App.tsx
import { Amplify } from 'aws-amplify';
import amplifyconfig from '../amplifyconfiguration.json';
// Fix the oauth redirects - DOn't think this is relevant but including it anyway
amplifyconfig.oauth.redirectSignIn = 'xxx';
amplifyconfig.oauth.redirectSignOut = 'xxx';
amplifyconfig.oauth.domain = 'xxx';
Amplify.configure(amplifyconfig);
// models/User.ts
import { graphql, GeneratedQuery } from '../core/graphql';
const getUser = /* GraphQL */ `
query GetUser($id: ID!) {
getUser(id: $id) {
${userObject}
}
` as GeneratedQuery<GetUserQueryVariables, GetUserQuery>;
export const find = async (id: string) => {
// console.debug(`User.find(${id})`);
const response = await graphql({
query: getUser,
variables: { id },
});
const user = response.data?.getUser as User | undefined;
if (!user) {
return null;
}
return transformUser(user);
};
// grapql.ts - My abstraction
import { generateClient } from 'aws-amplify/api';
// NOTE: Create an issue upstream to export this type
export type GeneratedQuery<InputType, OutputType> = string & {
__generatedQueryInput: InputType;
__generatedQueryOutput: OutputType;
};
const client = generateClient();
// Define a generic type for a function
type Func<T extends any[], R> = (...args: T) => R; // eslint-disable-line @typescript-eslint/no-explicit-any
const wait = (ms: number) => new Promise((res) => { setTimeout(res, ms); });
const maxRetries = 3;
const retry = <T extends any[], R>(originalFunction: Func<T, R>): Func<T, Promise<R>> => async (...args: T) => { // eslint-disable-line @typescript-eslint/no-explicit-any
let retries = 0;
let error: Error;
for (;;) {
try {
return originalFunction(...args);
} catch (err) {
console.debug(err);
retries += 1;
const errors = (err as { errors: Error[] }).errors || [];
[error] = errors;
if (error && error.message === 'Network Error') {
if (retries > maxRetries) {
Sentry.captureMessage('GraphQL Network Error: Too many retries', { extra: { json: JSON.stringify(err), retries } });
throw err;
}
console.error('We got a network error, we should retry', retries);
await wait(2 ** retries * 10); // eslint-disable-line no-await-in-loop
} else {
// If max retries reached, throw the last error
console.error('GraphQL Error2', err);
Sentry.captureMessage(`GraphQL Error ${args[0]}`, { extra: { errors: JSON.stringify(errors), retries, data: JSON.stringify(err, null, 2).slice(0, 200) } });
throw err;
}
}
}
};
export const graphql = retry(client.graphql);
Hi @johnf, thank you for the code examples!
I was able to reproduce the issue locally and have created a sample app for the team to use to investigate the issue further.
https://github.com/chrisbonifacio/v6-repro-12632
cc @david-mcafee
@johnf @david-mcafee
Want to note that I also happened to observe successful API calls if I moved the client generation to the App.tsx file, after the Amplify.configure
call.
simplified App.tsx
/**
* Sample React Native App
* https://github.com/facebook/react-native
*
* @format
*/
import React from 'react';
import {Button, SafeAreaView, StatusBar, useColorScheme} from 'react-native';
import {Colors} from 'react-native/Libraries/NewAppScreen';
import {Amplify} from 'aws-amplify';
import amplifyconfig from './src/amplifyconfiguration.json';
import {generateClient} from 'aws-amplify/api';
import {createUser} from './src/graphql/mutations';
Amplify.configure(amplifyconfig);
const client = generateClient();
function App(): JSX.Element {
const isDarkMode = useColorScheme() === 'dark';
const backgroundStyle = {
backgroundColor: isDarkMode ? Colors.darker : Colors.lighter,
};
return (
<SafeAreaView style={backgroundStyle}>
<StatusBar
barStyle={isDarkMode ? 'light-content' : 'dark-content'}
backgroundColor={backgroundStyle.backgroundColor}
/>
<Button
title="Create User"
onPress={async () => {
const user = await client.graphql({
query: createUser,
variables: {
input: {
username: 'cbonif',
},
},
});
console.log({user});
}}
/>
</SafeAreaView>
);
}
export default App;
@chrisbonifacio, when are you going to release the fix? Having the same issue, but want to avoid patch-package
usage for now...
Hey @johnf, it looks like you're creating a wrapper that ultimately invokes client.graphql()
separately from the intended instance context. I.e., the this
that the graphql()
method sees in your call chain would be the global
object. And, that wouldn't be expected to function correctly. This would explain why adding that .bind()
solves the issue for you.
A future-proof solution I've seen a lot for this type of problem is to create a simple "intermediate" lambda whenever you're creating a "reference" to an instance method. That is, use () => a.b()
whenever you're tempted to pass method a.b
around. FWIW, in my own code, unless I know that an object is designed for me to refer directly to its instance methods to be able to call them "out of context", I will defensively create these wrappers just about every time.
In your code, I think you'd need to change this line:
export const graphql = retry(client.graphql);
To something like this:
export const graphql = retry(((...args) => client.graphql(...args)) as typeof client.graphql);
Can you try that and let us know if that solves the problem for you?
(Assuming I've diagnosed this correctly (pending your response), we'll need to chat internally about if and how we can broadly support the call pattern you're after here.)
@svidgen I can confirm that does fix it.
I got this error in my monorepo with PNPM when some packages used Amplify v5 and others, v6. Once I removed the packages using Amplify v5, this error was gone.
Closing this issue as the original author is unblocked and this does not seem to be a bug.
Others running into this issue or similar, please refer to https://github.com/aws-amplify/amplify-js/issues/12632#issuecomment-1856314608
Before opening, please confirm:
JavaScript Framework
React Native
Amplify APIs
GraphQL API
Amplify Categories
api
Environment information
Describe the bug
I'm seeing the following error when trying to use the graphql API and API calls fail.
This occurs at https://github.com/aws-amplify/amplify-js/blob/main/packages/api-graphql/src/utils/resolveConfig.ts#L13 in
resoveConfig
I've tracked the core issue down to https://github.com/aws-amplify/amplify-js/blob/main/packages/api-graphql/src/internals/v6.ts#L115-L121
It is passing the amplify object as
this[___amplify]
but the client hasn't been bound to thegraphql
functionA quick fix is to bind it at https://github.com/aws-amplify/amplify-js/blob/main/packages/api-graphql/src/internals/generateClient.ts#L23-L40
It's quite possible I'm doing something wrong as I'm not sure how this could work for anyone as is
Expected behavior
API calls succeed without any issues.
Reproduction steps
Set up a standard amplify app and use a graphql query
Code Snippet
Log output
aws-exports.js
No response
Manual configuration
No response
Additional configuration
No response
Mobile Device
No response
Mobile Operating System
No response
Mobile Browser
No response
Mobile Browser Version
No response
Additional information and screenshots
No response