adding-entity-types
Adding Entity Types
Overview
Implement new Saleor entity types following a 7-step pipeline: schema, GraphQL operations, repository, bulk mutations, service, tests, and deployment stage. Each step builds on the previous, producing a complete entity module.
When to Use
- Implementing a new Saleor entity (e.g., categories, products, menus)
- Adding a new module to
src/modules/ - Creating features that need full CRUD with the Saleor API
- Not for: Modifying existing entities (see
understanding-saleor-domain)
Quick Reference
| Step | Output | Key File |
|---|---|---|
| 1. Schema | Zod validation | src/modules/config/schema/<entity>.schema.ts |
| 2. GraphQL | gql.tada operations | src/modules/<entity>/operations.ts |
| 3. Repository | Data access | src/modules/<entity>/repository.ts |
| 4. Bulk Mutations | Chunked processing | Integrated in repository |
| 5. Service | Business logic | src/modules/<entity>/service.ts |
| 6. Tests | Vitest + MSW | src/modules/<entity>/*.test.ts |
| 7. Pipeline | Deployment stage | src/modules/deployment/stages/ |
E2E Workflow
+-----------------+
| 1. Zod Schema | Define validation + infer types
+--------+--------+
v
+-----------------+
| 2. GraphQL Ops | gql.tada queries/mutations
+--------+--------+
v
+-----------------+
| 3. Repository | Data access + error wrapping
+--------+--------+
v
+-----------------+
| 4. Bulk Mutations| Chunking + error policies
+--------+--------+
v
+-----------------+
| 5. Service | Business logic + orchestration
+--------+--------+
v
+-----------------+
| 6. Tests | Unit + integration + builders
+--------+--------+
v
+-----------------+
| 7. Pipeline | Add deployment stage
+-----------------+
Step-by-Step Implementation
Step 1: Define Zod Schema
Create in src/modules/config/schema/<entity>.schema.ts. Key patterns:
- Infer types with
z.infer<typeof schema>(never manual type definitions) - Use branded types for slugs/names (e.g.,
transform((v) => v as EntitySlug)) - Use discriminated unions for variant types
See references/zod-schemas.md for detailed patterns.
Step 2: Create GraphQL Operations
Define in src/modules/<entity>/operations.ts. Key patterns:
- Import
graphqlfrom@/lib/graphql/graphql(gql.tada auto-types) - Use
ResultOf<typeof Query>for type inference - Always include
errors { field, message, code }in mutations - Follow naming:
get<Entity>Query,create<Entity>Mutation,bulk<Action><Entity>Mutation
See references/graphql-operations.md for detailed patterns.
Step 3: Implement Repository
Create src/modules/<entity>/repository.ts. Key patterns:
- Define interface (e.g.,
EntityOperations) for testability - Wrap errors with
GraphQLError.fromCombinedError() - Always check both
result.errorandresult.data?.mutation?.errors
See references/repository-service.md for detailed patterns.
Step 4: Add Bulk Mutations
Integrate chunked processing in repository. Key patterns:
- Use
processInChunksfrom@/lib/utils/chunked-processor - Threshold:
BulkOperationThresholds.DEFAULT(10 items) - Chunk size:
ChunkSizeConfig.DEFAULT_CHUNK_SIZE(10 items/chunk) - Error policy:
IGNORE_FAILED(continue processing, report all failures)
See references/bulk-mutations.md for detailed patterns.
Step 5: Create Service Layer
Create src/modules/<entity>/service.ts. Key patterns:
- Inject repository via constructor (interface, not concrete class)
- Validate inputs with Zod schema before passing to repository
- Orchestrate bulk operations and collect results
See references/repository-service.md for detailed patterns.
Step 6: Write Tests
Create in src/modules/<entity>/. Key patterns:
- Use test builders with fluent interface (
entityBuilder().withName("Test").build()) - Mock repositories with
vi.fn()for unit tests - Use MSW for integration tests with real GraphQL operations
- Validate builder output with Zod schema in
build()
See references/testing.md for detailed patterns.
Step 7: Add to Deployment Pipeline
Create stage in src/modules/deployment/stages/<entity>-stage.ts:
- Set
orderafter dependencies (entities this depends on) - Skip if no config entries:
if (!config.entities?.length) return { skipped: true } - Delegate to service layer
Validation Checkpoints
| Phase | Validate | Command |
|---|---|---|
| Schema | Types compile | npx tsc --noEmit |
| GraphQL | Schema matches | pnpm fetch-schema |
| Repository | Unit tests | pnpm test src/modules/<entity>/repository.test.ts |
| Service | Integration | pnpm test src/modules/<entity>/service.test.ts |
| Pipeline | E2E flow | See validating-pre-commit skill |
Common Mistakes
| Mistake | Fix |
|---|---|
| Not using branded types | Use EntitySlug or EntityName types for domain safety |
| Skipping bulk mutations | Use bulk for >1 item, always |
| Missing error wrapping | Wrap with GraphQLError.fromCombinedError() |
| Hardcoded pagination | Use first: 100 with pagination support |
Not checking errors array |
Always check result.data?.mutation?.errors |
Architecture Decision Summary
| Decision | Choice | Rationale |
|---|---|---|
| Type source | Zod schemas | Single source of truth, runtime validation |
| GraphQL typing | gql.tada | Auto-inference from schema |
| Bulk threshold | 10 items | Balance granularity vs performance |
| Error policy | IGNORE_FAILED | Continue processing, report all failures |
| Chunking | 10 items/chunk | Rate limit compliance |
Troubleshooting
Schema Won't Compile
| Symptom | Cause | Fix |
|---|---|---|
Type 'string' is not assignable |
Missing branded type transform | Add .transform((v) => v as EntitySlug) |
| Circular type reference | Schema imports forming cycle | Extract shared primitives to primitives.ts |
z.infer returns any |
Schema not exported correctly | Check exports in index.ts |
GraphQL Errors
| Symptom | Cause | Fix |
|---|---|---|
Cannot query field |
Schema drift | Run pnpm fetch-schema to update local schema |
Variable not provided |
Missing required variable | Check operation variables match gql.tada types |
Permission denied |
Token lacks scope | Verify token has required permissions in Saleor Dashboard |
Deployment Stage Failures
| Symptom | Cause | Fix |
|---|---|---|
| Stage skipped unexpectedly | Config section empty or misspelled | Check top-level key name in config.yml |
| Dependency entity missing | Stage order incorrect | Verify order is after dependency stages |
| Bulk operation partial failure | Some items invalid | Check IGNORE_FAILED error policy output for details |
Rollback
If a deployment stage fails mid-execution:
- Run
pnpm dev introspectto capture the current (partially modified) state - Compare with git history to identify what changed
- Manually fix or re-deploy with corrected config
Related Skills
- Domain concepts: See
understanding-saleor-domain - Testing details: See
analyzing-test-coverage - Zod patterns: See
designing-zod-schemas - GraphQL details: See
writing-graphql-operations - Code quality: See
reviewing-typescript-code