Creates GraphQL queries and mutations using gql.tada and urql. Implements repository pattern with type-safe operations and error handling...
Guide the creation and maintenance of GraphQL operations following project conventions for type safety, organization, error handling, and testing with gql.tada and urql.
| Tool | Purpose |
|---|---|
| gql.tada | Type-safe GraphQL with TypeScript inference |
| urql | GraphQL client with caching |
| @urql/exchange-auth | Authentication (Bearer token) |
| @urql/exchange-retry | Rate limit retry (429, max 5 attempts) |
src/lib/graphql/
āāā client.ts # urql client configuration
āāā operations/ # GraphQL operation definitions
āāā fragments/ # Reusable GraphQL fragments
āāā __mocks__/ # MSW test mocks
āāā schema.graphql # Saleor schema (generated)
src/modules/<entity>/
āāā repository.ts # Uses GraphQL operations
āāā ...
Use gql.tada for all operations. Types are inferred from schema automatically:
import { graphql } from 'gql.tada';
export const GetCategoriesQuery = graphql(`
query GetCategories($first: Int!) {
categories(first: $first) {
edges {
node { id, name, slug, description }
}
}
}
`);
// Type inferred automatically
type GetCategoriesResult = ResultOf<typeof GetCategoriesQuery>;
For shared fields, extract fragments and pass as second argument to graphql().
Each entity has a repository class that encapsulates GraphQL operations and maps responses to domain models:
export class CategoryRepository {
constructor(private readonly client: Client) {}
async findAll(): Promise<Category[]> {
const result = await this.client.query(GetCategoriesQuery, { first: 100 });
if (result.error) {
throw GraphQLError.fromCombinedError(result.error, 'GetCategories');
}
return this.mapCategories(result.data?.categories);
}
async create(input: CategoryInput): Promise<Category> {
const result = await this.client.mutation(CreateCategoryMutation, { input });
if (result.error) {
throw GraphQLError.fromCombinedError(result.error, 'CreateCategory');
}
if (result.data?.categoryCreate?.errors?.length) {
throw new GraphQLError('Category creation failed', result.data.categoryCreate.errors);
}
return this.mapCategory(result.data?.categoryCreate?.category);
}
}
Key pattern: Always map GraphQL responses to domain models in the repository. Never expose GraphQL types to services.
Two error types to always check:
result.error): Wrap with GraphQLError.fromCombinedError(error, 'OperationName', { context })result.data?.mutation?.errors): Check array length, throw with field detailsSee references/error-handling.md for complete error patterns, classification, and MSW error mocking.
pnpm fetch-schema # Updates schema.graphql and graphql-env.d.ts
Update schema when: new Saleor features needed, after Saleor version upgrade, or when encountering schema drift errors. Always commit schema changes with the feature implementation.
Mock GraphQL operations with MSW using graphql.query() and graphql.mutation() handlers. See analyzing-test-coverage skill for full MSW setup patterns.
The urql client is configured in src/lib/graphql/client.ts with: cacheExchange, authExchange (Bearer token), retryExchange (1s-15s backoff, 5 attempts, retries on 429 and network errors), and fetchExchange.
Do:
gql.tada for all operations (automatic type inference)Don't:
| Phase | Validate | Command |
|---|---|---|
| Schema fresh | No drift | pnpm fetch-schema |
| Operations typed | gql.tada inference | Check IDE types |
| Mocks match | MSW handlers | pnpm test |
| Error handling | All paths covered | Code review |
| Mistake | Fix |
|---|---|
Not checking errors array |
Always check result.data?.mutation?.errors |
| Exposing GraphQL types | Map to domain types in repository |
| Missing error context | Include operation name in errors |
| Stale schema | Run pnpm fetch-schema after Saleor updates |
| Not using fragments | Extract shared fields to fragments |
For up-to-date library docs, use Context7 MCP:
resolve-library-id with /urql-graphql/urqlresolve-library-id with "gql.tada"src/lib/graphql/client.ts - Client configurationsrc/lib/graphql/operations/ - Existing operationsdocs/CODE_QUALITY.md#graphql--external-integrations - Quality standardsadding-entity-types for full implementation including bulk mutationsadding-entity-types/references/bulk-mutations.md for chunking patternsanalyzing-test-coverage for MSW setup| Error | Cause | Fix |
|---|---|---|
CombinedError: [Network] |
API unreachable or URL malformed | Verify --url ends with /graphql/ and instance is running |
CombinedError: [GraphQL] |
Invalid query or variables | Run pnpm fetch-schema and check operation against schema |
result.data?.mutation?.errors non-empty |
Saleor validation rejection | Read field and message from errors array for specifics |
TypeError: Cannot read property of undefined |
Missing null check on response | Always check result.data before accessing nested properties |
| HTTP 429 (rate limited) | Too many requests | Built-in retry exchange handles this; increase delay if persistent |
pnpm fetch-schema ā ensures local schema matches remoteResultOf<typeof Query> in IDE to confirm typesFor a condensed quick reference, see .claude/rules/graphql-patterns.md (automatically loaded when editing GraphQL operations and repository files).