# Multi-Schema Support This document describes the multi-schema GraphQL support in Saleor Dashboard, which allows the application to work with both production (main) and staging (staging) API schemas. ## Overview The dashboard supports dual GraphQL schemas: - **Production Schema (main)**: The default schema used in production environments - **Staging Schema (staging)**: Optional schema for testing upcoming API changes Both schemas are generated at build time, and runtime selection is controlled by the `FF_USE_STAGING_SCHEMA` feature flag. ## Architecture ### Schema Files ``` schema-main.graphql # Production schema (Saleor stable tags like 3.22, 3.23) schema-staging.graphql # Staging schema (Saleor staging - main branch) schema.graphql # Symlink to schema-main.graphql (for tooling compatibility) ``` ### Generated Files Each schema generates its own set of TypeScript files: ``` src/graphql/ ├── hooks.generated.ts # Hooks from main schema ├── types.generated.ts # Types from main schema ├── typePolicies.generated.ts # Type policies from main schema ├── fragmentTypes.generated.ts # Fragment types from main schema ├── hooksStaging.generated.ts # Hooks from staging schema ├── typesStaging.generated.ts # Types from staging schema ├── typePoliciesStaging.generated.ts # Type policies from staging schema ├── fragmentTypesStaging.generated.ts # Fragment types from staging schema └── staging/ └── index.ts # Convenience export for all staging files ``` ## Usage ### Environment Configuration Add this variable to your `.env` file: ```env # Enable staging schema (default: false) FF_USE_STAGING_SCHEMA=false ``` The same `API_URL` is used for both schema versions. The FF_USE_STAGING_SCHEMA flag controls which schema types and hooks are used in the application. ### Importing GraphQL Hooks By default, the application imports from the production schema: ```typescript // Default import uses production schema (main) import { useProductListQuery } from "@dashboard/graphql"; ``` To explicitly use the staging schema, you have two options: **Option 1: Import from the staging directory (recommended for multiple imports)** ```typescript // Import from staging directory - includes all staging types, hooks, and helpers import { useProductListQuery, ProductListQuery, isStagingSchema } from "@dashboard/graphql/staging"; ``` **Option 2: Import directly from generated files** ```typescript // Import directly from specific generated files import { useProductListQuery } from "@dashboard/graphql/hooksStaging.generated"; import type { ProductListQuery } from "@dashboard/graphql/typesStaging.generated"; ``` ### Runtime Schema Selection Use the schema version helpers to check which schema is active: ```typescript import { isStagingSchema, getSchemaVersion } from "@dashboard/graphql"; function MyComponent() { // Check if staging schema is enabled if (isStagingSchema()) { // Use staging-specific features } // Or get the version string const version = getSchemaVersion(); // "main" or "staging" } ``` ### Conditional Feature Implementation For features that differ between schemas, you can conditionally use different hooks: **Example 1: Skip-based approach (recommended)** ```typescript import { isMainSchema, isStagingSchema, useProductListQuery } from "@dashboard/graphql"; import { useProductListQuery as useProductListQueryStaging } from "@dashboard/graphql/staging"; function ProductList() { // Execute only the relevant query based on schema version const { data: dataMain } = useProductListQuery({ skip: isStagingSchema() }); const { data: dataStaging } = useProductListQueryStaging({ skip: isMainSchema() }); // Use whichever data is available const data = dataStaging ?? dataMain; return
{/* Use data */}
; } ``` **Example 2: Dynamic hook selection** ```typescript import { isStagingSchema } from "@dashboard/graphql"; import { useProductListQuery as useProductListQueryMain } from "@dashboard/graphql"; import { useProductListQuery as useProductListQueryStaging } from "@dashboard/graphql/staging"; function ProductList() { // Choose the appropriate hook based on schema version const useProductList = isStagingSchema() ? useProductListQueryStaging : useProductListQueryMain; const { data } = useProductList({ variables: { /* ... */ } }); return
{/* ... */}
; } ``` ### Organizing Schema-Specific Queries For queries that only exist in one schema version, you can organize them in separate files: ``` src/products/ ├── queries.ts # Main schema queries └── queries.staging.ts # Staging-specific queries ``` ## Development ### Fetching Schemas ```bash # Fetch both schemas pnpm run fetch-schema # Fetch individual schemas pnpm run fetch-schema:main pnpm run fetch-schema:staging ``` ### Generating Types ```bash # Generate types for both schemas pnpm run generate # Generate for individual schemas pnpm run generate:main pnpm run generate:staging ``` ### Type Checking The standard type checking commands work with the multi-schema setup: ```bash # Type check with both schemas pnpm run check-types ``` ## How It Works ### Build Time 1. Both schemas are fetched from the Saleor repository 2. GraphQL Codegen generates separate TypeScript files for each schema 3. Production schema generates base types (no suffix) 4. Staging schema generates Staging-suffixed document variables ### Runtime 1. `FF_USE_STAGING_SCHEMA` feature flag determines which schema is active 2. Apollo Client loads the appropriate `fragmentTypes` based on the flag 3. `getApiUrl()` returns the same API URL regardless of the flag 4. Application code imports hooks from the appropriate generated file ## Important Notes ### Hook and Type Names - **Hook names are identical** in both versions (e.g., `useProductListQuery`) - **Type names are identical** in both versions (e.g., `ProductListQuery`) - GraphQL document variables have Staging suffix in the staging version (e.g., `ProductListStaging`) This means you cannot import both versions in the same file without aliasing: ```typescript // This works - import with aliases (recommended: use staging directory) import { useProductListQuery as useProductListQueryMain } from "@dashboard/graphql"; import { useProductListQuery as useProductListQueryStaging } from "@dashboard/graphql/staging"; // This also works - import directly from generated files import { useProductListQuery as useProductListQueryMain } from "@dashboard/graphql/hooks.generated"; import { useProductListQuery as useProductListQueryStaging } from "@dashboard/graphql/hooksStaging.generated"; // This doesn't work - naming conflict import { useProductListQuery } from "@dashboard/graphql"; import { useProductListQuery } from "@dashboard/graphql/staging"; // ERROR! ``` ### Staging Directory Convenience Export The `src/graphql/staging/index.ts` file provides a convenient way to import all staging-related exports: ```typescript // Instead of importing from multiple files... import { useProductListQuery } from "@dashboard/graphql/hooksStaging.generated"; import type { ProductListQuery } from "@dashboard/graphql/typesStaging.generated"; import { isStagingSchema } from "@dashboard/graphql/schemaVersion"; // You can import everything from one place import { useProductListQuery, ProductListQuery, isStagingSchema } from "@dashboard/graphql/staging"; ``` This export includes: - All staging hooks (`hooksStaging.generated.ts`) - All staging types (`typesStaging.generated.ts`) - Type policies (`typePoliciesStaging.generated.ts`) - Fragment types (`fragmentTypesStaging.generated.ts`) - Schema version helpers (`schemaVersion.ts`) - Extended types (`extendedTypes.ts`) ### Single Apollo Client The application uses a single Apollo Client instance that connects to the configured API_URL. The same API endpoint is used for both schema versions - only the client-side schema types and fragmentTypes differ based on the FF_USE_STAGING_SCHEMA flag. ### Schema Compatibility When `FF_USE_STAGING_SCHEMA=false` (default): - Uses production schema (main) - Apollo Client uses main fragmentTypes - All imports from `@dashboard/graphql` use production types When `FF_USE_STAGING_SCHEMA=true`: - Uses staging schema (staging) - Apollo Client uses staging fragmentTypes - Must explicitly import from Staging generated files for schema-specific features - Connects to the same API_URL (you must know what schema version is provided by this endpoint) ## Adding New Queries/Mutations ### For Queries/Mutations That Work with Both Schemas 1. Add your query/mutation to the appropriate file (e.g., `src/products/queries.ts`) 2. Run `pnpm run generate` to generate hooks for both schemas 3. Import from the default export for production schema: ```typescript import { useMyNewQuery } from "@dashboard/graphql"; ``` 4. For staging-specific features, import from the staging directory: ```typescript import { useMyNewQuery } from "@dashboard/graphql/staging"; ``` ### For Staging-Only Queries If a query uses fields that only exist in the staging schema: 1. Create a separate file (e.g., `src/products/queries.staging.ts`) 2. Define your staging-specific query there 3. Run `pnpm run generate:staging` to generate hooks 4. Import from the staging directory: ```typescript import { useMyNewQuery } from "@dashboard/graphql/staging"; ``` This keeps schema-specific code organized and prevents type errors when generating main schema types. ## Testing ### Testing with Different Schemas 1. Set `FF_USE_STAGING_SCHEMA=false` in your `.env` 2. Start the dev server: `pnpm run dev` 3. Test production schema (main) behavior Then: 1. Set `FF_USE_STAGING_SCHEMA=true` in your `.env` 2. Restart the dev server 3. Test staging schema (staging) behavior Note: Your API must support both schema versions for this to work correctly. ### CI/CD Considerations - Both schemas must generate successfully for builds to pass - Type checking validates both schema versions - Consider running E2E tests against both schemas ## Future Improvements ### Potential Optimizations 1. **Differential Generation**: Generate only changed types for Staging to reduce bundle size (this should not be a huge deal if we migrate from enums to type literals) 2. **Automatic Schema Switching**: Detect API version from response headers 3. **Schema Compatibility Checker**: Tool to validate backward compatibility 4. **Runtime Type Validation**: Validate API responses match the active schema ### Migration Path As new schema versions are released: 1. Update `fetch-schema:staging` script to fetch the new version 2. Run `pnpm run generate` 3. Fix any type errors in application code 4. Test thoroughly with the new schema 5. When ready, promote staging to main by updating `fetch-schema:main` to point to the new version ## Related Documentation ### Configuration Files - [Main Schema Codegen Configuration](../codegen-main.ts) - [Staging Schema Codegen Configuration](../codegen-staging.ts) - [Environment Variables](../.env.template) - [GraphQL Schema Configuration](../graphql.config.ts)