aws-amplify / amplify-js

A declarative JavaScript library for application development using cloud services.
https://docs.amplify.aws/lib/q/platform/js
Apache License 2.0
9.44k stars 2.13k forks source link

Attempting to use Amplify in an Appsync Typescript custom handler not working #13982

Open mmixan opened 2 weeks ago

mmixan commented 2 weeks ago

Before opening, please confirm:

JavaScript Framework

Next.js

Amplify APIs

GraphQL API

Amplify Version

v6

Amplify Categories

auth, api

Backend

Amplify Gen 2

Environment information

``` # Put output below this line System: OS: Windows 11 10.0.26100 CPU: (32) x64 13th Gen Intel(R) Core(TM) i9-13900HX Memory: 13.22 GB / 31.70 GB Binaries: Node: 20.5.1 - C:\Program Files\nodejs\node.EXE npm: 9.8.0 - C:\Program Files\nodejs\npm.CMD Browsers: Edge: Chromium (130.0.2849.46) Internet Explorer: 11.0.26100.1882 npmPackages: %name%: 0.1.0 @ampproject/toolbox-optimizer: undefined () @aws-amplify/backend: ^1.2.1 => 1.2.2 @aws-amplify/backend-cli: ^1.2.6 => 1.2.7 @aws-amplify/ui-react: ^6.1.13 => 6.5.3 @aws-amplify/ui-react-internal: undefined () @aws-amplify/ui-react-server: undefined () @babel/core: undefined () @babel/runtime: 7.22.5 @edge-runtime/cookies: 4.0.2 @edge-runtime/ponyfill: 2.4.1 @edge-runtime/primitives: 4.0.2 @hapi/accept: undefined () @mswjs/interceptors: undefined () @napi-rs/triples: undefined () @next/font: undefined () @next/react-dev-overlay: undefined () @nextui-org/button: 2.0.38 => 2.0.38 @nextui-org/code: 2.0.33 => 2.0.33 @nextui-org/input: 2.2.5 => 2.2.5 @nextui-org/kbd: 2.0.34 => 2.0.34 @nextui-org/link: 2.0.35 => 2.0.35 @nextui-org/listbox: 2.1.27 => 2.1.27 @nextui-org/navbar: 2.0.37 => 2.0.37 @nextui-org/snippet: 2.0.43 => 2.0.43 @nextui-org/switch: 2.0.34 => 2.0.34 @nextui-org/system: 2.2.6 => 2.2.6 @nextui-org/theme: 2.2.11 => 2.2.11 @opentelemetry/api: undefined () @react-aria/ssr: 3.9.4 => 3.9.4 (3.9.6) @react-aria/visually-hidden: 3.8.12 => 3.8.12 @stylistic/eslint-plugin-js: ^2.8.0 => 2.9.0 @testing-library/jest-dom: ^6.6.2 => 6.6.2 @testing-library/react: ^16.0.1 => 16.0.1 @testing-library/user-event: ^14.5.2 => 14.5.2 @types/http-errors: ^2.0.4 => 2.0.4 @types/node: 20.5.7 => 20.5.7 (20.16.11) @types/react: 18.3.3 => 18.3.3 @types/react-dom: 18.3.0 => 18.3.0 @typescript-eslint/eslint-plugin: 7.2.0 => 7.2.0 @typescript-eslint/parser: 7.2.0 => 7.2.0 @vercel/nft: undefined () @vercel/og: 0.6.2 @vitest/coverage-v8: ^2.1.3 => 2.1.3 acorn: undefined () amphtml-validator: undefined () anser: undefined () arg: undefined () assert: undefined () async-retry: undefined () async-sema: undefined () autoprefixer: 10.4.19 => 10.4.19 aws-amplify: ^6.4.0 => 6.6.2 aws-amplify/adapter-core: undefined () aws-amplify/analytics: undefined () aws-amplify/analytics/kinesis: undefined () aws-amplify/analytics/kinesis-firehose: undefined () aws-amplify/analytics/personalize: undefined () aws-amplify/analytics/pinpoint: undefined () aws-amplify/api: undefined () aws-amplify/api/server: undefined () aws-amplify/auth: undefined () aws-amplify/auth/cognito: undefined () aws-amplify/auth/cognito/server: undefined () aws-amplify/auth/enable-oauth-listener: undefined () aws-amplify/auth/server: undefined () aws-amplify/data: undefined () aws-amplify/data/server: undefined () aws-amplify/datastore: undefined () aws-amplify/in-app-messaging: undefined () aws-amplify/in-app-messaging/pinpoint: undefined () aws-amplify/push-notifications: undefined () aws-amplify/push-notifications/pinpoint: undefined () aws-amplify/storage: undefined () aws-amplify/storage/s3: undefined () aws-amplify/storage/s3/server: undefined () aws-amplify/storage/server: undefined () aws-amplify/utils: undefined () aws-cdk: ^2 => 2.159.1 aws-cdk-lib: ^2 => 2.159.1 babel-packages: undefined () browserify-zlib: undefined () browserslist: undefined () buffer: undefined () bytes: undefined () ci-info: undefined () cli-select: undefined () client-only: 0.0.1 clsx: 2.1.1 => 2.1.1 (1.2.1) comment-json: undefined () compression: undefined () conf: undefined () constants-browserify: undefined () constructs: ^10.3.0 => 10.3.0 content-disposition: undefined () content-type: undefined () cookie: undefined () cross-spawn: undefined () crypto-browserify: undefined () css.escape: undefined () data-uri-to-buffer: undefined () dayjs: ^1.11.13 => 1.11.13 debug: undefined () devalue: undefined () domain-browser: undefined () edge-runtime: undefined () esbuild: ^0.23.1 => 0.23.1 (0.21.5) eslint: ^8.57.0 => 8.57.1 eslint-config-next: ^14.2.13 => 14.2.15 eslint-config-prettier: ^8.2.0 => 8.10.0 eslint-plugin-import: ^2.26.0 => 2.31.0 eslint-plugin-jsx-a11y: ^6.4.1 => 6.10.0 eslint-plugin-node: ^11.1.0 => 11.1.0 eslint-plugin-prettier: ^5.1.3 => 5.2.1 eslint-plugin-react: ^7.23.2 => 7.37.1 eslint-plugin-react-hooks: ^4.6.0 => 4.6.2 eslint-plugin-testing-library: ^6.4.0 => 6.4.0 eslint-plugin-unused-imports: ^3.2.0 => 3.2.0 eslint-plugin-vitest: ^0.5.4 => 0.5.4 events: undefined () find-cache-dir: undefined () find-up: undefined () framer-motion: ~11.1.1 => 11.1.9 fresh: undefined () get-orientation: undefined () glob: undefined () gzip-size: undefined () http-errors: ^2.0.0 => 2.0.0 http-proxy: undefined () http-proxy-agent: undefined () https-browserify: undefined () https-proxy-agent: undefined () icss-utils: undefined () ignore-loader: undefined () image-size: undefined () intl-messageformat: ^10.5.0 => 10.7.0 is-animated: undefined () is-docker: undefined () is-wsl: undefined () jest-worker: undefined () jsdom: ^25.0.1 => 25.0.1 json5: undefined () jsonwebtoken: undefined () loader-runner: undefined () loader-utils: undefined () lodash.curry: undefined () lru-cache: undefined () micromatch: undefined () mini-css-extract-plugin: undefined () nanoid: undefined () native-url: undefined () neo-async: undefined () next: 14.1.1 => 14.1.1 next-themes: ^0.2.1 => 0.2.1 node-fetch: undefined () node-html-parser: undefined () ora: undefined () os-browserify: undefined () p-limit: undefined () path-browserify: undefined () platform: undefined () postcss: 8.4.38 => 8.4.38 (8.4.31, 8.4.47) postcss-flexbugs-fixes: undefined () postcss-modules-extract-imports: undefined () postcss-modules-local-by-default: undefined () postcss-modules-scope: undefined () postcss-modules-values: undefined () postcss-preset-env: undefined () postcss-safe-parser: undefined () postcss-scss: undefined () postcss-value-parser: undefined () process: undefined () punycode: undefined () querystring-es3: undefined () raw-body: undefined () react: ^18 => 18.3.1 react-builtin: undefined () react-dom: ^18 => 18.3.1 react-dom-builtin: undefined () react-dom-experimental-builtin: undefined () react-experimental-builtin: undefined () react-is: 18.2.0 react-refresh: 0.12.0 react-server-dom-turbopack-builtin: undefined () react-server-dom-turbopack-experimental-builtin: undefined () react-server-dom-webpack-builtin: undefined () react-server-dom-webpack-experimental-builtin: undefined () regenerator-runtime: 0.13.4 sass-loader: undefined () scheduler-builtin: undefined () scheduler-experimental-builtin: undefined () schema-utils: undefined () semver: undefined () send: undefined () server-only: 0.0.1 setimmediate: undefined () shell-quote: undefined () source-map: undefined () stacktrace-parser: undefined () stream-browserify: undefined () stream-http: undefined () string-hash: undefined () string_decoder: undefined () strip-ansi: undefined () superstruct: undefined () tailwind-variants: 0.1.20 => 0.1.20 tailwindcss: 3.4.3 => 3.4.3 tar: undefined () terser: undefined () text-table: undefined () timers-browserify: undefined () tsx: ^4.19.0 => 4.19.1 tty-browserify: undefined () typescript: ^5.6.2 => 5.6.2 (4.4.4, 4.9.5) ua-parser-js: undefined () unistore: undefined () util: undefined () vitest: ^2.1.3 => 2.1.3 vm-browserify: undefined () watchpack: undefined () web-vitals: undefined () webpack: undefined () webpack-sources: undefined () ws: undefined () zod: undefined () npmGlobalPackages: corepack: 0.19.0 nextui-cli: 0.3.4 npm: 9.8.0 ```

Describe the bug

I have been working with the new Gen 2 Amplify stuff recently and really liking it. I am specifically working on trying to move into custom handlers for the Appsync/GraphQL stuff to add custom business logic into those operations.

From the front-end, everything works fine when using code like client.models.XXX.get or client.mutations.XXX, but when I attempt to use those things in my custom handler I am having problems.

First, I tried this approach:

import type { Schema } from '@/amplify/data/resource'
import { generateClient } from 'aws-amplify/data'
import { Amplify } from 'aws-amplify'
import outputs from '@/amplify_outputs.json'
import { Game } from '@/models/game'

Amplify.configure(outputs)
const client = generateClient<Schema>()

export const createGame = async (): Promise<Game> => {
  const { errors, data } = await client.mutations.createNewGame()
  ...

This works great in development but throws the following errors during build:

2024-10-31T03:11:13.154Z [INFO]: # Executing command: npx ampx pipeline-deploy --branch $AWS_BRANCH --app-id $AWS_APP_ID
2024-10-31T03:11:28.361Z [INFO]: SyntaxError: TypeScript validation check failed.
Resolution: Fix the syntax and type errors in your backend definition.
Cause: amplify/data/createGameHandler.ts(6,21): error TS2307: Cannot find module '../../amplify_outputs.json' or its corresponding type declarations.

I tried using TS ignore approaches and tried relative and absolute path changes but nothing seemed to allow the code to find the amplify_outputs.json file during build.

So next, using https://docs.amplify.aws/react/build-a-backend/data/custom-business-logic/connect-bedrock/ as an example, I was attempting to remove the need for Amplify.configure() in the Handler, so I moved to a solution like this:

import type { Schema } from './resource'
import { generateClient } from 'aws-amplify/data'
import { Game } from '@/models/game'

const client = generateClient<Schema>()

export const handler: Schema['createNewGame']['functionHandler'] = async () => {
  const { errors, data } = await client.mutations.createNewGame()
  ...

This allows the build to work, but during runtime I receive the following error in the browser console:

message: "Client could not be generated. This is likely due to `Amplify.configure()` not being called prior to `generateClient()` or because the configuration passed to `Amplify.configure()` is missing GraphQL provider configuration."

Expected behavior

I would expect either of the above solutions to work, because either the amplify_outputs.json file is available during build and runtime, or that the amplify_outputs.json file is not required in the handler and therefore code is not required at runtime to initialize Amplfy.

Reproduction steps

  1. Use the https://docs.amplify.aws/react/start/ guide to create a basic Amplify App.
  2. Create a custom model in the data/resource file.
  3. Add a custom handler that requires access to the client.models or client.mutations functions.
  4. Note that things work well in development, but you encounter issues in build or runtime in production.

Code Snippet

// data/resource.ts
const schema = a.schema({
  Game: a
    .model({
       ....
    })
    .authorization((allow) => [ allow.publicApiKey() ]),

  createNewGame: a
    .mutation()
    .returns(a.ref('Game'))
    .authorization((allow) => [ allow.publicApiKey() ])
    .handler(a.handler.function(defineFunction({
      entry: './createGameHandler.ts',
    }))),

// data/createGameHandler.ts
import type { Schema } from './resource'
import { generateClient } from 'aws-amplify/data'
import { Game } from '@/models/game'

const client = generateClient<Schema>()

export const handler: Schema['createNewGame']['functionHandler'] = async () => {
  // custom business logic here
  const { errors, data } = await client.models.Game.create({})
  ...

Log output

``` // Put your logs below this line ```

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

chrisbonifacio commented 2 weeks ago

Hi @mmixan we're still in the process of adding model based graphql operations support for lambda. in the meantime, we have this documentation for accessing your graphql api using a client.graphql approach rather than client.models.

please follow this doc and let us know if you run into any issues:

https://docs.amplify.aws/react/build-a-backend/data/customize-authz/grant-lambda-function-access-to-api/#access-the-api-using-aws-amplify

i've marked this as a feature request and will circle back once we release it

jflaflamme commented 2 weeks ago

Wish I had seen this post 2 days ago. Spent some several hours on this. Looking forward for the updated doc.

mmixan commented 2 weeks ago

Thank you @chrisbonifacio !
I had seen that doc before, but was hoping there was something easier to work with. I spent some time with that article the other day and it made sense, but I am struggling a bit with one line: endpoint: env.<amplifyData>_GRAPHQL_ENDPOINT, // replace with your defineData name

Can you give a concrete example of what that would look like in practice? I have the generated code exported into a folder near the custom handler, but I am not following how to get to the URL in doc. Thanks!