amplify-js: Property 'subscribe' does not exist on type 'Promise>'. TS2339

Describe the bug

Property 'subscribe' does not exist on type 'Promise<GraphQLResult<object>> | Observable<object>'.
  Property 'subscribe' does not exist on type 'Promise<GraphQLResult<object>>'.  TS2339

    91 |       console.log('start subscription to sensors');
    92 |
  > 93 |       const subscriber = API.graphql(graphqlOperation(onCreateSensorValues)).subscribe({
       |                                                                              ^
    94 |         next: (response: ISensorsSubscriptionResponse) => {
    95 |
    96 |           //update the sensor's status in state

To Reproduce Steps to reproduce the behavior: FYI, I’m running this sample, Everything worked just fine until I upgraded aws-amplify to 3.x.

Expected behavior A clear and concise description of what you expected to happen.

What is Configured?

  System:
    OS: macOS 10.15.7
    CPU: (8) x64 Intel(R) Core(TM) i7-7820HQ CPU @ 2.90GHz
    Memory: 142.06 MB / 16.00 GB
    Shell: 5.7.1 - /bin/zsh
  Binaries:
    Node: 12.18.4 - /usr/local/bin/node
    Yarn: 1.22.5 - ~/.yarn/bin/yarn
    npm: 6.14.11 - ~/n/bin/npm
    Watchman: 4.9.0 - /usr/local/bin/watchman
  Browsers:
    Chrome: 88.0.4324.96
    Safari: 14.0.1
  npmPackages:
    @aws-amplify/ui-react: ^0.2.34 => 0.2.34 
    @babel/cli: ^7.10.1 => 7.10.1 
    @babel/core: ^7.10.2 => 7.10.2 
    @babel/preset-env: ^7.10.2 => 7.10.2 
    @material-ui/core: ^4.8.3 => 4.10.1 
    @material-ui/icons: ^4.5.1 => 4.9.1 
    @testing-library/jest-dom: ^4.2.4 => 4.2.4 
    @testing-library/react: ^9.4.0 => 9.5.0 
    @testing-library/user-event: ^7.2.1 => 7.2.1 
    @types/chart.js: ^2.9.11 => 2.9.21 
    @types/jest: ^24.0.25 => 24.9.1 
    @types/node: ^12.12.24 => 12.12.47 
    @types/react: ^16.9.17 => 16.9.36 
    @types/react-chartjs-2: ^2.5.7 => 2.5.7 
    @types/react-dom: ^16.9.4 => 16.9.8 
    @types/react-map-gl: ^5.1.0 => 5.2.4 
    @types/react-router-dom: ^5.1.3 => 5.1.5 
    aws-amplify: ^3.3.14 => 3.3.14 
    aws-amplify-react: ^3.1.6 => 3.1.9 
    chart.js: ^2.9.3 => 2.9.3 
    react: ^16.12.0 => 16.13.1 
    react-chartjs-2: ^2.9.0 => 2.9.0 
    react-dom: ^16.12.0 => 16.13.1 
    react-map-gl: ^5.2.1 => 5.2.7 
    react-router-dom: ^5.1.2 => 5.2.0 
    react-scripts: ^3.3.0 => 3.4.1 
    typescript: ^3.7.4 => 3.9.5 
  npmGlobalPackages:
    @aws-amplify/cli: 4.41.2
    @nestjs/cli: 7.5.1
    @snow-tree/camera-probe: 7.0.13
    appcenter-cli: 2.7.3
    claudia: 5.12.0
    firebase-tools: 8.16.2
    ios-deploy: 1.11.3
    iot-solutions: 3.1.1
    npm: 6.14.11
    serve: 11.3.2
    snyk: 1.437.3

Additional context #5447

About this issue

  • Original URL
  • State: closed
  • Created 3 years ago
  • Reactions: 15
  • Comments: 17 (3 by maintainers)

Most upvoted comments

Hi,

The API.graphql return types were improved by #9197 , the PR has some information on how to use it, but in summary, there are two new types you can import and pass to hint the compiler of your intent (GraphQLQuery and GraphQLSubscription)

import { GraphQLSubscription, GraphQLQuery } from "@aws-amplify/api";

const res = await API.graphql<GraphQLQuery<MyType>>({ query: 'query' }); // GraphQLResult<MyType>
const res2 = API.graphql<GraphQLSubscription<MyType>>({ query: 'query' }); // Observable<GraphQLResult<MyType>>

Any updates on this? The casting used to work (on older applications) however, now I get

Conversion of type 'Promise<GraphQLResult<any>> | Observable<object>' to type 'Observable<object>' may be a mistake because neither type sufficiently overlaps with the other. If this was intentional, convert the expression to 'unknown' first.
  Type 'Observable<object>' is missing the following properties from type 'Observable<object>': concat, [Symbol.observable]

How do we get around it?

We’ve added TS docs on how you can specify the type and the generic type parm for subscriptions here: https://docs.amplify.aws/lib/graphqlapi/subscribe-data/q/platform/js/#using-amplify-graphql-client

If you’re writing your own GraphQL subscription query (as opposed to using one of the Amplify-generated queries), you can do the following:

import { API, graphqlOperation } from 'aws-amplify';
import type { GraphQLSubscription } from '@aws-amplify/api';

type OnCreateTodo = {
  onCreateTodo?:  {
    id: string,
    name: string,
    description?: string | null,
  } | null,
};

const ON_CREATE_TODO = /* GraphQL */ `
  subscription OnCreateTodo {
    onCreateTodo {
      id
      name
      description
    }
  }
`;

const sub = API.graphql<GraphQLSubscription<OnCreateTodo>>(
  graphqlOperation(ON_CREATE_TODO)
).subscribe({
  next: ({ provider, value }) => console.log({ provider, value }),
  error: (error) => console.warn(error)
});

That being said, as Jim pointed out above, we’re currently working on improving and simplifying TS support for Amplify JS. But hopefully this gets folks unblocked for the time being.

if you are trying to make a subscription, you need to narrow down the type to an observable since it can be a promise or an observable. You can do what with the typescript Exclude utility

API.graphql(graphqlOperation(yourOperation)) as Exclude<
        ReturnType<typeof API.graphql>,
        Promise<any>
      >
    ).subscribe({
      next: ({ value }: { value: unknown }) => console.log('value', value),
      error: (error: unknown) => console.log('error', error),
    });

I had to cast it as an Observable:

import {Observable} from "zen-observable-ts";
const observable = GraphQLAPI.graphql(graphqlOperation(XXXXX)) as Observable<any>;
observable.subscribe(...)

I had to cast it as an Observable:

import {Observable} from "zen-observable-ts";
const observable = GraphQLAPI.graphql(graphqlOperation(XXXXX)) as Observable<any>;
observable.subscribe(...)

But this does not allow to unsubscribe.

Property 'unsubscribe' does not exist on type 'Observable<any>'. Did you mean 'subscribe'?ts(2551)

I had to cast it as an Observable:

import {Observable} from "zen-observable-ts";
const observable = GraphQLAPI.graphql(graphqlOperation(XXXXX)) as Observable<any>;
observable.subscribe(...)

This worked for me. I had to install the same version that was used in the amplify libraries though zen-observable-ts@0.8.19

for amplify version 6 i was able to get it to work with GraphQLSubscription and Subscription

import { Amplify } from "aws-amplify";
import type { Subscription } from "rxjs";
import { generateClient, GraphQLSubscription } from "aws-amplify/api";

const client = generateClient();

interface IChatResponseSubscription {
  subscribe: {
    sender: string;
    conversation_id: string;
    content: string;
  };
}

const chatResponseSubscriptionQuery = /* GraphQL */ `
  subscription ChatResponseSubscription($conversation_id: String!) {
    subscribe(conversation_id: $conversation_id) {
      content
      conversation_id
      sender
    }
  }
`;
const subscribeRef = useRef<Subscription | null>(null);

useEffect(() => {
  const subscribeToChatResponse = async (conversationId: string) => {
    try {
      const subscribe = client
        .graphql<GraphQLSubscription<IChatResponseSubscription>>({
          query: chatResponseSubscriptionQuery,
          variables: {
            conversation_id: conversationId,
          },
        })
        .subscribe({
          next: (value) => {
            handleResponseSubscription(value.data);
          },
          error: (error) => console.warn(error),
        });

      subscribeRef.current = subscribe;
    } catch (error) {
      const errorMsg = "Error retrieving response";
      console.log(conversationId, errorMsg);
    }
  };

  if (conversationId) {
    subscribeToChatResponse(conversationId);
  }

  return () => {
    if (subscribeRef?.current) {
      subscribeRef?.current?.unsubscribe();
    }
  };
}, [conversationId, handleResponseSubscription]);

Having the same problem, not sure how to take advantage from those new GraphQLQuery and GraphQLSubscription types mentioned by @manueliglesias , I ended up doing below (type cast as any), my subscription still seems working ok:

  subscription.value = (API.graphql(graphqlOperation(
    SUBSCRIBE_USER,
    { id: userId }
  )) as any).subscribe({ next: onSubscribe })
// . . .
  (subscription.value as any).unsubscribe()

I was able to resolve this by wrapping the subscription in an async function in my useEffect, I was not getting the correct auto completion typing but I also was not getting errors. I was able to get the value return typed via a custom type that used the type generated by codegen

type SubscriptionValue = {
  value: {
    data: OnUpdateGoalSubscription;
  };
};
export default function GoalPage({ goal }: { goal: GoalType }) {

  const [currentGoal, setCurrentGoal] = useState<GoalType>(goal);

  useEffect(() => {
    let subscription;
    const initSubscription = async () => {
      const user = await Auth.currentAuthenticatedUser();
      subscription = API.graphql({
        query: onUpdateGoal,
        variables: { owner: user.username },
      }).subscribe({
        next: ({ value }: SubscriptionValue) =>
          setCurrentGoal(value.data.onUpdateGoal),
        error: ({ error }) => console.warn(error.errors[0]),
      });
    };
    initSubscription();
    return () => {
      subscription.unsubscribe();
    };
  }, []);

This is what worked for me:

const onUpdateFileImportData = (await API.graphql({
          query: onUpdateFileImportDataSubscription,
          variables: {
            input: {
              id: fileImportDataId
            }
          }
        })) as Observable<OnUpdateFileImportDataSubscription>

I got OnUpdateFileImportDataSubscription from the ./src/API.ts file which is generated by running amplify codegen.

However, there seems to be a key missing as when I do this:

const subscription = onUpdateFileImportData.subscribe({
          next: (updatedFileImportDataRes: OnUpdateFileImportDataSubscription) => {
....

I get this error: Property 'value' does not exist on type 'OnUpdateFileImportDataSubscription'. when retrieving the updated record like so: const updatedFileImportDataId = updatedFileImportDataRes.value.data.onUpdateFileImportData.id

This is what the OnUpdateFileImportDataSubscription type looks like:

(alias) type OnUpdateFileImportDataSubscription = {
    onUpdateFileImportData?: {
        __typename: 'FileImportData';
        id: string;
        fileName?: string | null | undefined;
        totalImportedCourse?: number | null | undefined;
        newCourse?: number | null | undefined;
        coursesToResolve?: number | ... 1 more ... | undefined;
        ... 6 more ...;
        courseDatas?: {
            ...;
        } | ... 1 more ... | undefined;
    } | null | undefined;
}

A temporary fix is to change OnUpdateFileImportDataSubscription in the next function to any or create a custom type.

Is there any documentation anywhere on how to use Typescript with subscriptions? 😞