Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Custom Transformer v2 - Error: Template asset can be used only with TransformerStackSynthesizer #9362

Closed
4 tasks done
jgo80 opened this issue Dec 21, 2021 · 9 comments · Fixed by #10188
Closed
4 tasks done
Assignees
Labels
bug Something isn't working graphql-transformer-v1 Issue related to GraphQL Transformer v1 p2 question General question

Comments

@jgo80
Copy link

jgo80 commented Dec 21, 2021

Before opening, please confirm:

  • I have installed the latest version of the Amplify CLI (see above), and confirmed that the issue still persists.
  • I have searched for duplicate or closed issues.
  • I have read the guide for submitting bug reports.
  • I have done my best to include a minimal, self-contained set of instructions for consistently reproducing the issue.

How did you install the Amplify CLI?

curl

If applicable, what version of Node.js are you using?

v17.0.1

Amplify CLI Version

7.6.4

What operating system are you using?

Mac

Amplify Categories

api

Amplify Commands

Not applicable

Describe the bug

When adding a slot to an existing resolver, the following error is thrown: Template asset can be used only with TransformerStackSynthesizer. It refers to amplify-graphql-transformer-core/src/cdk-compat/file-asset.ts

Expected behavior

I expect additional VTL files to be automatically generated when adding a slot. In my example Mutation.createTodo.auth.2.req.vtl

The Transformer v2 is very sparsely documented - any fix, help or workaround is appreciated 😥

Reproduction steps

  1. Init amplify
  2. Create GraphQL API with Basic Cognito Auth and the Demo "Todo" GraphQL Schema
  3. Create a Custom Transformer (code below), configure transform.conf.json, add @Tenant directive to GraphQL Schema
  4. Run amplify push

Custom Transformer code:

import {
  MappingTemplate,
  TransformerPluginBase,
} from '@aws-amplify/graphql-transformer-core';
import {
  TransformerContextProvider,
  TransformerResolverProvider,
  TransformerSchemaVisitStepContextProvider,
} from '@aws-amplify/graphql-transformer-interfaces';
import { DirectiveNode, ObjectTypeDefinitionNode } from 'graphql';
import { printBlock, raw } from 'graphql-mapping-template';

const directiveName = 'tenant';
const directiveDefinition = `
  directive @${directiveName} on OBJECT
`;

export class TenantTransformer extends TransformerPluginBase {
  private typesWithModelDirective: Set<string> = new Set();

  constructor() {
    super('tenant-transformer', directiveDefinition);
  }

  object = (
    definition: ObjectTypeDefinitionNode,
    directive: DirectiveNode,
    ctx: TransformerSchemaVisitStepContextProvider,
  ): void => {
    const typeName = definition.name.value;
    this.typesWithModelDirective.add(typeName);
  };

  generateResolvers = (ctx: TransformerContextProvider): void => {
    const resolver = ctx.resolvers.getResolver(
      'Mutation',
      'createTodo',
    ) as TransformerResolverProvider;

    const snippet = printBlock(
      `Debug: Check if @tenant directive find its way to a VTL file`,
    )(raw('$util.unauthorized()'));

    resolver.addToSlot(
      'auth',
      MappingTemplate.s3MappingTemplateFromString(
        snippet,
        `Mutation.createTodo.{slotName}.{slotIndex}.req.vtl`,
      ),
    );
  };
}

GraphQL schema(s)

type Todo @model @tenant @auth(rules: [{ allow: private }]) {
  id: ID!
  name: String!
  description: String
}

Log output

✖ An error occurred when pushing the resources to the cloud
🛑 Template asset can be used only with TransformerStackSynthesizer
Error: Template asset can be used only with TransformerStackSynthesizer
    at new FileAsset (/Users/joey/Development/Playground/amplify-transformer-debug/tenant-transformer/node_modules/@aws-amplify/graphql-transformer-core/src/cdk-compat/file-asset.ts:40:13)
    at S3MappingTemplate.bind (/Users/joey/Development/Playground/amplify-transformer-debug/tenant-transformer/node_modules/@aws-amplify/graphql-transformer-core/src/cdk-compat/template-asset.ts:56:20)
    at new AppSyncFunctionConfiguration (/snapshot/node_modules/@aws-amplify/graphql-transformer-core/lib/appsync-function.js:11:62)
    at DefaultTransformHost.addAppSyncFunction (/snapshot/node_modules/@aws-amplify/graphql-transformer-core/lib/transform-host.js:81:20)
    at TransformerResolver.synthesizeResolvers (/snapshot/node_modules/@aws-amplify/graphql-transformer-core/lib/transformer-context/resolver.js:210:45)
    at TransformerResolver.synthesize (/snapshot/node_modules/@aws-amplify/graphql-transformer-core/lib/transformer-context/resolver.js:116:37)
    at GraphQLTransform.collectResolvers (/snapshot/node_modules/@aws-amplify/graphql-transformer-core/lib/transformation/transform.js:345:22)
    at GraphQLTransform.transform (/snapshot/node_modules/@aws-amplify/graphql-transformer-core/lib/transformation/transform.js:234:14)
    at _buildProject (/snapshot/node_modules/amplify-provider-awscloudformation/lib/graphql-transformer/transform-graphql-schema.js:511:37)
    at buildAPIProject (/snapshot/node_modules/amplify-provider-awscloudformation/lib/graphql-transformer/transform-graphql-schema.js:475:24)
    at transformGraphQLSchema (/snapshot/node_modules/amplify-provider-awscloudformation/lib/graphql-transformer/transform-graphql-schema.js:361:29)
    at Object.run (/snapshot/node_modules/amplify-provider-awscloudformation/lib/push-resources.js:221:5)
    at async Promise.all (index 0)
    at providersPush (/snapshot/node_modules/@aws-amplify/cli/lib/extensions/amplify-helpers/push-resources.js:105:5)
    at AmplifyToolkit.pushResources [as _pushResources] (/snapshot/node_modules/@aws-amplify/cli/lib/extensions/amplify-helpers/push-resources.js:72:17)
    at Object.executeAmplifyCommand (/snapshot/node_modules/@aws-amplify/amplify-category-api/lib/index.js:203:9)
    at executePluginModuleCommand (/snapshot/node_modules/@aws-amplify/cli/lib/execution-manager.js:164:5)
    at executeCommand (/snapshot/node_modules/@aws-amplify/cli/lib/execution-manager.js:35:9)
    at Object.run (/snapshot/node_modules/@aws-amplify/cli/lib/index.js:162:9)

Additional information

No response

@jgo80 jgo80 changed the title Custom Transformer v2 - Template asset can be used only with TransformerStackSynthesizer Custom Transformer v2 - Error: Template asset can be used only with TransformerStackSynthesizer Dec 21, 2021
@cjihrig
Copy link
Contributor

cjihrig commented Dec 22, 2021

It looks like you're hitting this exception because this instanceof check is failing. I believe it's failing because the Amplify CLI is resolving '@aws-amplify/graphql-transformer-core' to one location on disk, and your custom directive is resolving it to another, so you end up with multiple distinct TransformerStackSythesizer classes.

Since the CLI must be installed globally, it's probably easiest for your directive to rely on the global versions of the dependency.

@jgo80
Copy link
Author

jgo80 commented Dec 22, 2021

Since the CLI must be installed globally, it's probably easiest for your directive to rely on the global versions of the dependency.

@cjihrig thanks - I can follow your reasoning, however I'm puzzled. How would I achieve this? Relying on the global versions of the dependency? From where would I import this 🤷‍♂️

import {
  MappingTemplate,
  TransformerPluginBase,
} from '@aws-amplify/graphql-transformer-core';

I also had a look on a Custom Transformer V2 from another party, it is done the same way here, and it also does crash upon amplify push api.

Edit: I added the error log in the initial post above. It seems to confirm your theory. The error gets thrown by the local dependency while being invoked from the global version of the Amplify CLI.

@cjihrig
Copy link
Contributor

cjihrig commented Dec 22, 2021

From where would I import this

Node's module resolution algorithm searches some global installation locations by default (it's generally discouraged to rely on global installs, but since the CLI must be installed globally, 🤷). You can find out the exact path that a module is resolved to like this:

require.resolve('@aws-amplify/cli')

You can also figure out which paths the resolution algorithm will look in like this:

require.resolve.paths('@aws-amplify/cli')

If the modules that you need are installed globally, then you don't need to add them in your package.json (as that would install them in your local node_modules/ and cause this issue). Alternatively, you can pass an absolute path to require(), but this would probably not work well across machines.

@jgo80
Copy link
Author

jgo80 commented Dec 23, 2021

Thanks for your suggestion, @cjihrig - Unfortunately this does not work at all.

// globalPath.js
const globalPath = require.resolve('@aws-amplify/graphql-transformer-core');

Running the code above (node globalPath.js) returns

Error: Cannot find module '@aws-amplify/graphql-transformer-core'

Same with const require.resolve('@aws-amplify/cli')

Despite that, I cannot use this in TypeScript

const globalPath = require.resolve('@aws-amplify/graphql-transformer-core');
const { TransformerPluginBase, MappingTemplate } = require(globalPath);

export class TenantTransformer extends TransformerPluginBase {
  ...
}
'extends' clause of exported class 'TenantTransformer' has or is using private name 'TransformerPluginBase'.ts(4020)

Question: Are we facing a bug or badly designed Plugin System here? I assume that behavior is not intended.

What would be an appropriate label (bug, documentation) to bring this issue to the attention of the AWS Amplify CLI team? Our company's development work has stopped for over two weeks because we're not able to implement 3 important Custom Transformers V2. The official documentation about Adding Custom GraphQL Transformers to the Project is not helpful.

Thank you very much in advance.

@jgo80
Copy link
Author

jgo80 commented Dec 23, 2021

I found a workaround. The path below along with // @ts-ignore does the trick for me on my Mac, thanks for pointing into the right direction, @cjihrig. So for an intermediate resolution I will develop with the correct dependency and switch to the snapshot path before building the final code from TS.

// @ts-ignore
import { MappingTemplate, TransformerPluginBase} from '/snapshot/node_modules/@aws-amplify/graphql-transformer-core';

Still I feel this is not a good approach for a Plugin System. As you pointed out, this will probably not work across machines. Any comment/suggestion for a safe approach from AWS Amplify CLI team would be nice. I will leave this issue open.

@johnpc johnpc added pending-triage Issue is pending triage graphql-transformer-v1 Issue related to GraphQL Transformer v1 question General question and removed pending-triage Issue is pending triage labels Jan 14, 2022
@multimeric
Copy link

Indeed. I'm not sure using instanceof is a good idea ever when the JS ecosystem is always duplicating and compiling classes into other things.

I've hit this issue too, and at least in my case it's only triggered when using MappingTemplate.s3MappingTemplateFromString. When I switch to MappingTemplate.inlineTemplateFromString I can bypass the errorr.

@multimeric
Copy link

I realise this is happening because the amplify CLI is running via a global installation, with packages in /usr/lib, whereas your local plugin is using the same packages, installed locally to node_modules. Since these are different copies of the same code, any attempt to compare objects for exact equality will fail, and this includes checking the prototype of objects using instanceof.

Node's module resolution algorithm searches some global installation locations by default (it's generally discouraged to rely on global installs, but since the CLI must be installed globally, shrug).

I believe this is incorrect, as Node doesn't have this behaviour unlike say Python. I haven't tried this, but I wonder if setting export NODE_PATH=/usr/lib/node_modules and then uninstalling most of the amplify dependencies locally would work, as it's described here as a valid method of loading global modules.

@cjihrig
Copy link
Contributor

cjihrig commented Feb 23, 2022

I believe this is incorrect, as Node doesn't have this behaviour

It does. Node's module resolution algorithm is described here. Search for GLOBAL_FOLDERS. Using NODE_PATH might work for you, but it is somewhat discouraged in the docs.

I think the better short term solution is to investigate making the existing instanceof check more robust - there are other ways of doing type checking in JavaScript that work cross-realm.

@cjihrig cjihrig self-assigned this Feb 23, 2022
@cjihrig cjihrig added the bug Something isn't working label Feb 23, 2022
@multimeric
Copy link

Apologies, you're right. I guess because it's tricky to get Typescript to recognise these global libraries I assumed they weren't accessible.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
bug Something isn't working graphql-transformer-v1 Issue related to GraphQL Transformer v1 p2 question General question
Projects
None yet
6 participants