Skip to content

Editions & Markets

This is the canonical model for the Edition (formerly Environment) and Market (formerly Store) concepts, and the isolation guarantees between them.

The model

An Edition is an isolated container of the entire site. Markets live inside an Edition. Anything you change in an Edition — content and markets — stays in that Edition. Cloning an Edition deep-copies it; from then on the copy is independent. Nothing ever leaks back to another Edition.

Org → Space → Edition → Market → content (pages, per Locale)
│ └── Default market; others inherit it and override
└── isolated container; clone = fork; live pointer flips per Edition
  • Edition (was Environment): fall / spring / staging / live. The live delivery key targets one Edition.
  • Market (was Store): germany / uk / default. Edition-scoped. Each Edition has its own default market; non-default markets inherit the default and override per page.
  • Locale (unchanged): language within a market’s content.
  • Why full isolation incl. markets: an Edition stages a coherent set of planned changes (e.g. decommission a market for the Fall season) without touching the currently-live Edition. Partial isolation (content yes, markets no) is the footgun we removed.

Rename map (identifier-aware — NOT a blind s/store/market/)

fromto
word environmentedition
table environmentseditions
col environment_id (~20 tables)edition_id
api_keys.target_environment_id / environments[]target_edition_id / editions[]
/api/environments, /env/…, X-Alokai-CMS-Environment, ?environment=, ALOKAI_CMS_ENVIRONMENT…editions, /edition/…, X-Alokai-CMS-Edition, ?edition=, ALOKAI_CMS_EDITION
word storemarket
table storesmarkets
col store_id (~8 tables)market_id
/api/stores, /store/…, X-Alokai-CMS-Store, alokai_cms_store, ALOKAI_CMS_STORE[_*]…markets, /market/…, X-Alokai-CMS-Market, alokai_cms_market, ALOKAI_CMS_MARKET[_*]
default-store-${space} conventiondefault-market-${space}

The word “store” also hides in storefront / storage / restore / KV — replace by identifier, not by text.

Schema (rename + isolation)

  • environmentseditions; environment_idedition_id everywhere.
  • storesmarkets; add edition_id (NOT NULL, REFERENCES editions).
  • Market slug uniqueness becomes per (space, edition), not per space.
  • Each Edition has exactly one default market.
  • A page’s market_id must belong to the same Edition as its edition_id.
  • api_keys: a key targets one Edition; its market allow-list references that Edition’s market ids.

Behavior

  • Clone Edition: create the new Edition; copy its markets → new market rows (new ids, same slug, scoped to the new Edition); build a marketIdMap; copy pages/page_versions into the new Edition, remapping market_id via the map, preserving is_wildcard, published_version, etc. Selective: the operator picks which markets to bring (check-all default; the default market is pinned/always copied).
  • Delete Market: edition-scoped. Hand-cascade (D1 doesn’t reliably apply FK cascade) the market’s pages / page_versions / components / component_versions / assets / personalization_attributes / translation_jobs / design_system_settings within that Edition only, then the market row. Other Editions are untouched. You cannot delete an Edition’s default market (promote another first).
  • Delivery resolution: by (edition, market) with default-market fallback within the edition; market_id IS NULL resolves to that Edition’s default. Implemented once in apps/cms/src/server/utils/market-fallback.ts and shared by every read path (editor + all delivery variants). See Page Resolution for the full rules.

Deterministic market IDs

Cloning produces stable, derivable market IDs so cross-edition tooling can map them:

  • Default market: default-market-<editionId>
  • Non-default market: <oldMarketId>__<editionId>

Naming vs canonical identifier

Canonical names in code / DB / API are edition and market (consistent everywhere). A future, optional per-space display label (e.g. show “Store” or “Region” in the UI) would be a presentation feature layered on the canonical name — not an alias in the schema.