Skip to content

Latest commit

 

History

History
346 lines (243 loc) · 11.1 KB

File metadata and controls

346 lines (243 loc) · 11.1 KB

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:

# 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:

// 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)

// 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

// 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:

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)

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 <div>{/* Use data */}</div>;
}

Example 2: Dynamic hook selection

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 <div>{/* ... */}</div>;
}

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

# Fetch both schemas
pnpm run fetch-schema

# Fetch individual schemas
pnpm run fetch-schema:main
pnpm run fetch-schema:staging

Generating Types

# 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:

# 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:

// 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:

// 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:
    import { useMyNewQuery } from "@dashboard/graphql";
  4. For staging-specific features, import from the staging directory:
    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:
    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