Open harshit9715 opened 6 months ago
const graphApi = new AmplifyGraphqlApi(stack, "GraphqlApi", {
functionSlots: [...getCreateSlots],
definition: graphQLDefinition,
apiName: "GraphqlApi",
translationBehavior: {},
transformerPlugins: [],
authorizationModes: {
apiKeyConfig: {
expires: Duration.days(365),
description: "API Key for GraphQL API - " + stack.stage,
},
defaultAuthorizationMode: "OPENID_CONNECT",
oidcConfig: {
oidcIssuerUrl: process.env.CLERK_ISSUER_BASE_URL!,
oidcProviderName: "Clerk",
tokenExpiryFromAuth: Duration.millis(0),
tokenExpiryFromIssue: Duration.millis(0),
},
},
});
const MutationUpdateQuestionDataResolverFn = new Asset(
stack,
"ModifiedResolvers",
{
path: path.join("stacks/resolvers/Mutation.updateQuestion.req.vtl"),
}
);
graphApi.resources.cfnResources.cfnFunctionConfigurations[
"MutationUpdateQuestionDataResolverFn"
].requestMappingTemplateS3Location =
MutationUpdateQuestionDataResolverFn.s3ObjectUrl;
This is what I am using but it requires management overhead. It would be great if we can use the same slot behavior to override the data resolvers too.
Hi @harshit9715 - can you provide more context on the type of updates you'd like to override?
Sure!
I am trying to implement atomic counters for certain fields.
type Question @model {
id: ID!
content: String!
upvoteCount: Int! @default("0")
downvoteCount: Int! @default("0")
viewCount: Int! @default("0")
}
Now the vtl template does not support atomic counters out of the box but then I noticed the generated code is very close to what I need.
Basically, if there is a fieldName ending with Count, i am overriding its behaviour on update mutation.
Instead of using $expSet.put
i am using $expAdd.put
. This way I can simply add the value entered to the existing value. and I believe it would be write consistent. work with subscriptions and also return right value incase the value changes in the backend.
The changed lines
from this
#if( $util.isNull($entry.value) )
#set( $discard = $expRemove.add("#$entryKeyAttributeName") )
$util.qr($expNames.put("#$entryKeyAttributeName", "$entry.key"))
#else
$util.qr($expSet.put("#$entryKeyAttributeName", ":$entryKeyAttributeName"))
$util.qr($expNames.put("#$entryKeyAttributeName", "$entry.key"))
$util.qr($expValues.put(":$entryKeyAttributeName", $util.dynamodb.toDynamoDB($entry.value)))
#end
To this
#if( $util.isNull($entry.value) )
#set( $discard = $expRemove.add("#$entryKeyAttributeName") )
$util.qr($expNames.put("#$entryKeyAttributeName", "$entry.key"))
#else
#if ( $entry.key.endsWith("Count"))
#if ( $entry.value != 1 && $entry.value != -1) ## this condition makes sure that if the client sent anything other than +1 or -1, then no change on the value will occur.
#set( $entry.value = 0 )
#end
$util.qr($expAdd.put("#$entryKeyAttributeName", ":$entryKeyAttributeName")) ## NOTICE THE expAdd USED ON THIS LINE.
#else
$util.qr($expSet.put("#$entryKeyAttributeName", ":$entryKeyAttributeName"))
#end
## $util.qr($expSet.put("#$entryKeyAttributeName", ":$entryKeyAttributeName"))
$util.qr($expNames.put("#$entryKeyAttributeName", "$entry.key"))
$util.qr($expValues.put(":$entryKeyAttributeName", $util.dynamodb.toDynamoDB($entry.value)))
#end
Here is what was generated for Mutation.updateQuestion.req.vtl
## [Start] Mutation Update resolver. **
#set( $args = $util.defaultIfNull($ctx.stash.transformedArgs, $ctx.args) )
## Set the default values to put request **
#set( $mergedValues = $util.defaultIfNull($ctx.stash.defaultValues, {}) )
## copy the values from input **
$util.qr($mergedValues.putAll($util.defaultIfNull($args.input, {})))
## set the typename **
## Initialize the vars for creating ddb expression **
#set( $expNames = {} )
#set( $expValues = {} )
#set( $expSet = {} )
#set( $expAdd = {} )
#set( $expRemove = [] )
#if( $ctx.stash.metadata.modelObjectKey )
#set( $Key = $ctx.stash.metadata.modelObjectKey )
#else
#set( $Key = {
"id": $util.dynamodb.toDynamoDB($args.input.id)
} )
#end
## Model key **
#if( $ctx.stash.metadata.modelObjectKey )
#set( $keyFields = [] )
#foreach( $entry in $ctx.stash.metadata.modelObjectKey.entrySet() )
$util.qr($keyFields.add("$entry.key"))
#end
#else
#set( $keyFields = ["id"] )
#end
#foreach( $entry in $util.map.copyAndRemoveAllKeys($mergedValues, $keyFields).entrySet() )
#if( !$util.isNull($ctx.stash.metadata.dynamodbNameOverrideMap) && $ctx.stash.metadata.dynamodbNameOverrideMap.containsKey("$entry.key") )
#set( $entryKeyAttributeName = $ctx.stash.metadata.dynamodbNameOverrideMap.get("$entry.key") )
#else
#set( $entryKeyAttributeName = $entry.key )
#end
#if( $util.isNull($entry.value) )
#set( $discard = $expRemove.add("#$entryKeyAttributeName") )
$util.qr($expNames.put("#$entryKeyAttributeName", "$entry.key"))
#else
$util.qr($expSet.put("#$entryKeyAttributeName", ":$entryKeyAttributeName"))
$util.qr($expNames.put("#$entryKeyAttributeName", "$entry.key"))
$util.qr($expValues.put(":$entryKeyAttributeName", $util.dynamodb.toDynamoDB($entry.value)))
#end
#end
#set( $expression = "" )
#if( !$expSet.isEmpty() )
#set( $expression = "SET" )
#foreach( $entry in $expSet.entrySet() )
#set( $expression = "$expression $entry.key = $entry.value" )
#if( $foreach.hasNext() )
#set( $expression = "$expression," )
#end
#end
#end
#if( !$expAdd.isEmpty() )
#set( $expression = "$expression ADD" )
#foreach( $entry in $expAdd.entrySet() )
#set( $expression = "$expression $entry.key $entry.value" )
#if( $foreach.hasNext() )
#set( $expression = "$expression," )
#end
#end
#end
#if( !$expRemove.isEmpty() )
#set( $expression = "$expression REMOVE" )
#foreach( $entry in $expRemove )
#set( $expression = "$expression $entry" )
#if( $foreach.hasNext() )
#set( $expression = "$expression," )
#end
#end
#end
#set( $update = {} )
$util.qr($update.put("expression", "$expression"))
#if( !$expNames.isEmpty() )
$util.qr($update.put("expressionNames", $expNames))
#end
#if( !$expValues.isEmpty() )
$util.qr($update.put("expressionValues", $expValues))
#end
## Begin - key condition **
#if( $ctx.stash.metadata.modelObjectKey )
#set( $keyConditionExpr = {} )
#set( $keyConditionExprNames = {} )
#foreach( $entry in $ctx.stash.metadata.modelObjectKey.entrySet() )
$util.qr($keyConditionExpr.put("keyCondition$velocityCount", {
"attributeExists": true
}))
$util.qr($keyConditionExprNames.put("#keyCondition$velocityCount", "$entry.key"))
#end
$util.qr($ctx.stash.conditions.add($keyConditionExpr))
#else
$util.qr($ctx.stash.conditions.add({
"id": {
"attributeExists": true
}
}))
#end
## End - key condition **
#if( $args.condition )
$util.qr($ctx.stash.conditions.add($args.condition))
#end
## Start condition block **
#if( $ctx.stash.conditions && $ctx.stash.conditions.size() != 0 )
#set( $mergedConditions = {
"and": $ctx.stash.conditions
} )
#set( $Conditions = $util.parseJson($util.transform.toDynamoDBConditionExpression($mergedConditions)) )
#if( $Conditions.expressionValues && $Conditions.expressionValues.size() == 0 )
#set( $Conditions = {
"expression": $Conditions.expression,
"expressionNames": $Conditions.expressionNames
} )
#end
## End condition block **
#end
#set( $UpdateItem = {
"version": "2018-05-29",
"operation": "UpdateItem",
"key": $Key,
"update": $update
} )
#if( $Conditions )
#if( $keyConditionExprNames )
$util.qr($Conditions.expressionNames.putAll($keyConditionExprNames))
#end
$util.qr($UpdateItem.put("condition", $Conditions))
#end
$util.toJson($UpdateItem)
## [End] Mutation Update resolver. **
and here is what I override the template with.
## [Start] Mutation Update resolver. **
#set( $args = $util.defaultIfNull($ctx.stash.transformedArgs, $ctx.args) )
## Set the default values to put request **
#set( $mergedValues = $util.defaultIfNull($ctx.stash.defaultValues, {}) )
## copy the values from input **
$util.qr($mergedValues.putAll($util.defaultIfNull($args.input, {})))
## set the typename **
## Initialize the vars for creating ddb expression **
#set( $expNames = {} )
#set( $expValues = {} )
#set( $expSet = {} )
#set( $expAdd = {} )
#set( $expRemove = [] )
#if( $ctx.stash.metadata.modelObjectKey )
#set( $Key = $ctx.stash.metadata.modelObjectKey )
#else
#set( $Key = {
"id": $util.dynamodb.toDynamoDB($args.input.id)
} )
#end
## Model key **
#if( $ctx.stash.metadata.modelObjectKey )
#set( $keyFields = [] )
#foreach( $entry in $ctx.stash.metadata.modelObjectKey.entrySet() )
$util.qr($keyFields.add("$entry.key"))
#end
#else
#set( $keyFields = ["id"] )
#end
#foreach( $entry in $util.map.copyAndRemoveAllKeys($mergedValues, $keyFields).entrySet() )
#if( !$util.isNull($ctx.stash.metadata.dynamodbNameOverrideMap) && $ctx.stash.metadata.dynamodbNameOverrideMap.containsKey("$entry.key") )
#set( $entryKeyAttributeName = $ctx.stash.metadata.dynamodbNameOverrideMap.get("$entry.key") )
#else
#set( $entryKeyAttributeName = $entry.key )
#end
#if( $util.isNull($entry.value) )
#set( $discard = $expRemove.add("#$entryKeyAttributeName") )
$util.qr($expNames.put("#$entryKeyAttributeName", "$entry.key"))
#else
#if ( $entry.key.endsWith("Count"))
#if ( $entry.value != 1 && $entry.value != -1)
#set( $entry.value = 0 )
#end
$util.qr($expAdd.put("#$entryKeyAttributeName", ":$entryKeyAttributeName"))
#else
$util.qr($expSet.put("#$entryKeyAttributeName", ":$entryKeyAttributeName"))
#end
## $util.qr($expSet.put("#$entryKeyAttributeName", ":$entryKeyAttributeName"))
$util.qr($expNames.put("#$entryKeyAttributeName", "$entry.key"))
$util.qr($expValues.put(":$entryKeyAttributeName", $util.dynamodb.toDynamoDB($entry.value)))
#end
#end
#set( $expression = "" )
#if( !$expSet.isEmpty() )
#set( $expression = "SET" )
#foreach( $entry in $expSet.entrySet() )
#set( $expression = "$expression $entry.key = $entry.value" )
#if( $foreach.hasNext() )
#set( $expression = "$expression," )
#end
#end
#end
#if( !$expAdd.isEmpty() )
#set( $expression = "$expression ADD" )
#foreach( $entry in $expAdd.entrySet() )
#set( $expression = "$expression $entry.key $entry.value" )
#if( $foreach.hasNext() )
#set( $expression = "$expression," )
#end
#end
#end
#if( !$expRemove.isEmpty() )
#set( $expression = "$expression REMOVE" )
#foreach( $entry in $expRemove )
#set( $expression = "$expression $entry" )
#if( $foreach.hasNext() )
#set( $expression = "$expression," )
#end
#end
#end
#set( $update = {} )
$util.qr($update.put("expression", "$expression"))
#if( !$expNames.isEmpty() )
$util.qr($update.put("expressionNames", $expNames))
#end
#if( !$expValues.isEmpty() )
$util.qr($update.put("expressionValues", $expValues))
#end
## Begin - key condition **
#if( $ctx.stash.metadata.modelObjectKey )
#set( $keyConditionExpr = {} )
#set( $keyConditionExprNames = {} )
#foreach( $entry in $ctx.stash.metadata.modelObjectKey.entrySet() )
$util.qr($keyConditionExpr.put("keyCondition$velocityCount", {
"attributeExists": true
}))
$util.qr($keyConditionExprNames.put("#keyCondition$velocityCount", "$entry.key"))
#end
$util.qr($ctx.stash.conditions.add($keyConditionExpr))
#else
$util.qr($ctx.stash.conditions.add({
"id": {
"attributeExists": true
}
}))
#end
## End - key condition **
#if( $args.condition )
$util.qr($ctx.stash.conditions.add($args.condition))
#end
## Start condition block **
#if( $ctx.stash.conditions && $ctx.stash.conditions.size() != 0 )
#set( $mergedConditions = {
"and": $ctx.stash.conditions
} )
#set( $Conditions = $util.parseJson($util.transform.toDynamoDBConditionExpression($mergedConditions)) )
#if( $Conditions.expressionValues && $Conditions.expressionValues.size() == 0 )
#set( $Conditions = {
"expression": $Conditions.expression,
"expressionNames": $Conditions.expressionNames
} )
#end
## End condition block **
#end
#set( $UpdateItem = {
"version": "2018-05-29",
"operation": "UpdateItem",
"key": $Key,
"update": $update
} )
#if( $Conditions )
#if( $keyConditionExprNames )
$util.qr($Conditions.expressionNames.putAll($keyConditionExprNames))
#end
$util.qr($UpdateItem.put("condition", $Conditions))
#end
$util.toJson($UpdateItem)
## [End] Mutation Update resolver. **
const graphApi = new AmplifyGraphqlApi(stack, "GraphqlApi", { functionSlots: [...getCreateSlots], definition: graphQLDefinition, apiName: "GraphqlApi", translationBehavior: {}, transformerPlugins: [], authorizationModes: { apiKeyConfig: { expires: Duration.days(365), description: "API Key for GraphQL API - " + stack.stage, }, defaultAuthorizationMode: "OPENID_CONNECT", oidcConfig: { oidcIssuerUrl: process.env.CLERK_ISSUER_BASE_URL!, oidcProviderName: "Clerk", tokenExpiryFromAuth: Duration.millis(0), tokenExpiryFromIssue: Duration.millis(0), }, }, }); const MutationUpdateQuestionDataResolverFn = new Asset( stack, "ModifiedResolvers", { path: path.join("stacks/resolvers/Mutation.updateQuestion.req.vtl"), } ); graphApi.resources.cfnResources.cfnFunctionConfigurations[ "MutationUpdateQuestionDataResolverFn" ].requestMappingTemplateS3Location = MutationUpdateQuestionDataResolverFn.s3ObjectUrl;
This is what I am using but it requires management overhead. It would be great if we can use the same slot behavior to override the data resolvers too.
And I realised that the construct can use the local template if it is available. Here is the updated code.
const resolverConfig = graphApi.resources.cfnResources.cfnFunctionConfigurations[functionName];
if (!resolverConfig) return;
resolverConfig.requestMappingTemplateS3Location = undefined;
resolverConfig.requestMappingTemplate = fs.readFileSync(
path.join("stacks/resolvers", filePath),
"utf8"
);
Hi @harshit9715, this is intended workflow for overriding when using the construct directly. I'll leave this open as a feature request.
Amplify CLI Version
12.11.0
Question
I am using the CDK construct for building the graphql schema.
I want to know if there is a way of overriding some of the auto-generated resolvers?
This option is available through amplify cli. we just need to place the edited resolvers in the amplify/backend/api//resolvers folder with the same name.