Shopify / shopify-app-js

MIT License
219 stars 88 forks source link

Admin API GraphQL Codegen does not generate types for scalars #1154

Open sleepdotexe opened 6 days ago

sleepdotexe commented 6 days ago

Issue summary

Before opening this issue, I have:

I am using the GraphQL Codegen to automatically generate TypeScript types for my Shopify queries. I am running into issues using properties that have Scalar types, as these are shown as type any. For example, Product['created_at'] has type any, when it really should be string since it has the scalar type of DateTime which is an extension of a string.

The helper functions from @shopify/api-codegen-preset don't seem to provide scalar types by default, nor any way to manually provide these types. Since these types are managed by Shopify and relate specifically to the API, it would make the most sense to me for these to be automatically provided by the helper setup functions.

According to https://the-guild.dev/graphql/codegen/plugins/typescript/typescript#scalars, it should theoretically be possible to provide scalars to the codegen.

Here is an example .graphqlrc.ts:

import { ApiType, shopifyApiProject } from '@shopify/api-codegen-preset';

import CONSTANTS from './utils/constants';

const docs = [
    './utils/shopify/queries/**/*.{js,ts,jsx,tsx}',
    './utils/shopify/mutations/**/*.{js,ts,jsx,tsx}',
    './utils/shopify/fragments/**/*.{js,ts,jsx,tsx}',
];

const { apiVersion } = CONSTANTS.shopify; // ApiVersion.January24

export default {
    schema: `./types/shopify/admin-${apiVersion}.schema.json`,
    documents: docs,
    projects: {
        default: shopifyApiProject({
            apiType: ApiType.Admin,
            apiVersion: apiVersion,
            documents: docs,
            outputDir: './types/shopify',
        }),
    },
};

Expected behavior

Using the .graphqlrc.ts above, the inbuilt Shopify Scalars should be automatically generated in admin.types.d.ts like so:

/** All built-in and custom scalars, mapped to their actual values */
export type Scalars = {
  ID: { input: string; output: string; }
  String: { input: string; output: string; }
  Boolean: { input: boolean; output: boolean; }
  Int: { input: number; output: number; }
  Float: { input: number; output: number; }
  ARN: { input: any; output: any; }
  Date: { input: string; output: string; }
  DateTime: { input: string; output: string; }
  Decimal: { input: string; output: string; }
  FormattedString: { input: string; output: string; }
  HTML: { input: string; output: string; }
  JSON: { input: any; output: any; }
  Money: { input: string; output: string; }
  StorefrontID: { input: string; output: string; }
  URL: { input: string; output: string; }
  UnsignedInt64: { input: string; output: string; }
  UtcOffset: { input: string; output: string; }
};

...Which allows the properties to have correct types in files.

products[0].createdAt
            ^----- `createdAt: string | undefined`

Actual behavior

The Shopify scalars have types of any.

/** All built-in and custom scalars, mapped to their actual values */
export type Scalars = {
  ID: { input: string; output: string; }
  String: { input: string; output: string; }
  Boolean: { input: boolean; output: boolean; }
  Int: { input: number; output: number; }
  Float: { input: number; output: number; }
  ARN: { input: any; output: any; }
  Date: { input: any; output: any; }
  DateTime: { input: any; output: any; }
  Decimal: { input: any; output: any; }
  FormattedString: { input: any; output: any; }
  HTML: { input: any; output: any; }
  JSON: { input: any; output: any; }
  Money: { input: any; output: any; }
  StorefrontID: { input: any; output: any; }
  URL: { input: any; output: any; }
  UnsignedInt64: { input: any; output: any; }
  UtcOffset: { input: any; output: any; }
};

...Which means properties are not typed correctly.

products[0].createdAt
            ^----- `createdAt: any`

Steps to reproduce the problem

  1. Set up a typescript project using the Admin GraphQL API and codegen as per these instructions.
  2. Use the .graphqlrc.ts file as outlined above in this issue.
  3. Attempt to use a scalar property (eg. Product['createdAt']).

Debug logs

There is a temporary solution I've found – after using the shopifyApiProject() helper function, I managed to manually force the config to include scalars like so:

const outputDir = './types/shopify';
const { apiVersion } = CONSTANTS.shopify;

const setup: ReturnType<typeof shopifyApiProject> & {
    extensions: {
        codegen: { generates: { [key: string]: { config?: { scalars?: any } } } };
    };
} = shopifyApiProject({
    apiType: ApiType.Admin,
    apiVersion: apiVersion,
    documents: docs,
    outputDir,
});

setup.extensions.codegen.generates[`${outputDir}/admin.types.d.ts`].config = {
    minify: true,
    scalars: {
        Color: 'string',
        Date: 'string',
        DateTime: 'string',
        Decimal: 'string',
        FormattedString: 'string',
        HTML: 'string',
        Money: 'string',
        StorefrontID: 'string',
        URL: 'string',
        UnsignedInt64: 'string',
        UtcOffset: 'string',
    },
};

export default {
    schema: `${outputDir}/admin-${apiVersion}.schema.json`,
    documents: docs,
    projects: {
        default: setup,
    },
};

This seems to work, as the admin.types.d.ts file now includes the expected types for scalars. However it's not my favourite solution since it relies on hardcoding the scalar types, and also updating them if they are change (or new scalars are added) in future. Additionally, I have to mutate the setup object and provide a hack-y union type to keep type safety.

Could we have these scalars included by default? And if not, could we at least have a documented option added to the shopifyApiProject helper function that allows us to manually provide these without the workaround?

matteodepalo commented 5 days ago

Hi @sleepdotexe this is a great issue description and a good suggestion. We'll take a look at what we can add to shopifyApiProject, but in the meanwhile I'm glad that your workaround is working for you.