Security News
Research
Data Theft Repackaged: A Case Study in Malicious Wrapper Packages on npm
The Socket Research Team breaks down a malicious wrapper package that uses obfuscation to harvest credentials and exfiltrate sensitive data.
@shopify/graphql-codegen
Advanced tools
A codegen plugin and preset for generating TypeScript types from GraphQL queries in a `d.ts` file. It does not require any function wrapper and adds no runtime overhead (0 bytes to the bundle).
A codegen plugin and preset for generating TypeScript types from GraphQL queries in a d.ts
file. It does not require any function wrapper and adds no runtime overhead (0 bytes to the bundle).
This package was originally extracted from
@shopify/hydrogen-codegen
to be agnostic of Hydrogen and to be used with any GraphQL client.
The GraphQL client must use TypeScript interfaces that are extended in the generated d.ts
file. This package also exports utility types to create GraphQL clients that comply to these interfaces.
// `shop` is inferred as {name: string, description: string}
const {shop} = await clientQuery(`#graphql
query layout {
shop {
name
description
}
}
`);
It does not require wrapping your queries or transforming them to AST with graphql-tag
. As long as the GraphQL client complies with the TypeScript interfaces, you can simply pass plain strings.
It does not add any runtime overhead or size to the production bundle. The generated d.ts
file is used only for type checking, and optionally for importing types in your code.
Generated types can be imported in your components to declare the shape of the data you expect to receive. For example:
import type {LayoutQuery} from './my-api.generated';
export function Layout({shop}: LayoutQuery) {
return (
<div>
<h1>{shop.name}</h1>
<p>{shop.description}</p>
</div>
);
}
This plugin is intended for server-side queries. Since it doesn't rely on AST transformations, it might not be compatible with normalized GraphQL caches in the browser or persisted queries.
If you are using string interpolation to build your queries, you will need to use as const
on the strings to avoid assigning a generic string
type to the variable. For example:
const fragment = `#graphql
fragment MyFragment on Shop {
name
description
}
`;
const query = `#graphql
${fragment}
query layout {
shop {
...MyFragment
}
}
` as const; // <--- Add `as const` here
You can use the following example configuration with GraphQL CLI:
// <root>/codegen.ts
import type {CodegenConfig} from '@graphql-codegen/cli';
import {pluckConfig, preset} from '@shopify/graphql-codegen';
export default {
overwrite: true,
pluckConfig,
generates: {
'my-api.generated.d.ts': {
preset,
schema: './path/to/my-api-schema.json',
documents: ['./src/graphql/my-api/*.{ts,tsx,js,jsx}'],
presetConfig: {
// Generate the line `import type * as MyAPI from 'path/to/my-api-types';`.
// If you don't have generated types for your API beforehand,
// omit this parameter to generate the types inline.
importTypes: {
namespace: 'MyAPI',
from: 'path/to/my-api-types',
},
// Skip the __typename property from generated types:
skipTypenameInOperations: true,
// Add a custom interface extension to connect
// the generated types to the GraphQL client:
interfaceExtension: ({queryType, mutationType}) => `
declare module 'my-api-client' {
interface MyAPIQueries extends ${queryType} {}
interface MyAPIMutations extends ${mutationType} {}
}
`,
},
},
},
} as CodegenConfig;
[!TIP] To use enums instead of types, change the generated file extension to
.ts
instead of.d.ts
.
Then, include queries in your app that match the given schema and documents paths. For example, for a query layout
like the one in the example above, the generated d.ts
file will look like this:
// my-api.generated.d.ts
import type * as MyAPI from 'path/to/my-api-types';
export type LayoutQueryVariables = MyAPI.Exact<{[key: string]: never}>;
export type LayoutQuery = {
shop: Pick<MyAPI.Shop, 'name' | 'description'>;
};
interface GeneratedQueryTypes {
'#graphql\n query layout {\n shop {\n name\n description\n }\n }\n': {
return: LayoutQuery;
variables: LayoutQueryVariables;
};
}
interface GeneratedMutationTypes {}
declare module 'my-api-client' {
interface MyAPIQueries extends GeneratedQueryTypes {}
interface MyAPIMutations extends GeneratedMutationTypes {}
}
Therefore, when passing the query to the GraphQL client, TypeScript will infer the type of the response and the variables parameter.
To make a GraphQL client that complies with the generated types, you can use the following utility types:
// my-api-client.ts
import type {
ClientReturn,
ClientVariablesInRestParams,
} from '@shopify/graphql-codegen';
// Empty interface to be extended with the generated queries and mutations in user projects.
export interface MyAPIQueries {}
export async function clientQuery<
OverrideReturnType extends any = never,
RawGqlString extends string = string,
>(
query: RawGqlString,
...params: ClientVariablesInRestParams<MyAPIQueries, RawGqlString>
): Promise<ClientReturn<MyAPIQueries, RawGqlString, OverrideReturnType>> {
// The 'params' and 'params.variables' are optional
// if the query has no variables, required otherwise.
const {variables} = params[0] ?? {};
// Client implementation (this could be forwarding the query to Apollo, urql, useQuery, etc.)
const response = await fetch('https://my-api.com/graphql', {
method: 'POST',
headers: {'Content-Type': 'application/json'},
body: JSON.stringify({query, variables}),
});
return response.json();
}
This allows to use the clientQuery
function with any query string and TypeScript will infer the type of the response and the variables parameter. It's also possible to override the return type of the query by passing a type argument to clientQuery
:
// Inferred type from the query:
data = await clientQuery('query layout {shop {name}}');
// Overwritten return type:
data = await clientQuery<{shop: {name: string}}>('...');
The type utilities accept other optional type params:
ClientVariablesInRestParams<
// Interface extended with queries in the user project
MyAPIQueries,
// The raw query string that comes from the user
RawGqlString,
// -- Optional:
// Additional properties to the params object.
// E.g. `{cache: Cache; debug?: boolean}`
AdditionalParamOptions,
// Query variables that the client automatically injects.
// E.g. `'locale' | 'currency'`
AutoInjectedVariables
>;
To see a full example of a GraphQL client using these types, check the Hydrogen's Storefront client.
This preset can be wrapped in a package that generates types for a specific API and adds default values to the preset. For example, an all-in-one solution for an "Example API" would be a package that provides:
importTypes
option, or use the standalone @graphql-codegen/typescript
plugin to generate the types in a separate file.@shopify/graphql-codegen
's preset and provides default values like in the following example.// example-api/preset.ts
import {preset as internalPreset} from '@shopify/graphql-codegen';
export {pluckConfig} from '@shopify/graphql-codegen';
// Export a custom preset that adds default values to @shopify/graphql-codegen:
export const preset = {
buildGeneratesSection: (options) => {
return internalPreset.buildGeneratesSection({
...options,
// Known schema path from the example-api package:
schema: 'example-api/schema.json'
presetConfig: {
importTypes: {
namespace: 'ExampleAPI',
// Known types path from the example-api package:
from: 'example-api/types',
},
interfaceExtension: ({queryType, mutationType}) => `
declare module 'example-api/client' {
interface AdminQueries extends ${queryType} {}
interface AdminMutations extends ${mutationType} {}
}`,
},
});
},
};
When the user imports this new preset, they don't need to specify the schema and types paths:
// <root>/codegen.ts
// This uses @shopify/graphql-codegen internally:
import {preset, pluckConfig} from 'example-api/preset';
export default {
overwrite: true,
pluckConfig,
generates: {
'example-api.generated.d.ts': {
preset,
documents: ['**/*.ts'],
},
},
};
FAQs
A codegen plugin and preset for generating TypeScript types from GraphQL queries in a `d.ts` file. It does not require any function wrapper and adds no runtime overhead (0 bytes to the bundle).
We found that @shopify/graphql-codegen demonstrated a healthy version release cadence and project activity because the last version was released less than a year ago. It has 24 open source maintainers collaborating on the project.
Did you know?
Socket for GitHub automatically highlights issues in each pull request and monitors the health of all your open source dependencies. Discover the contents of your packages and block harmful activity before you install or update your dependencies.
Security News
Research
The Socket Research Team breaks down a malicious wrapper package that uses obfuscation to harvest credentials and exfiltrate sensitive data.
Research
Security News
Attackers used a malicious npm package typosquatting a popular ESLint plugin to steal sensitive data, execute commands, and exploit developer systems.
Security News
The Ultralytics' PyPI Package was compromised four times in one weekend through GitHub Actions cache poisoning and failure to rotate previously compromised API tokens.