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/)
| from | to |
|---|---|
word environment | edition |
table environments | editions |
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 store | market |
table stores | markets |
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} convention | default-market-${space} |
The word “store” also hides in storefront / storage / restore / KV —
replace by identifier, not by text.
Schema (rename + isolation)
environments→editions;environment_id→edition_ideverywhere.stores→markets; addedition_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_idmust belong to the same Edition as itsedition_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, remappingmarket_idvia the map, preservingis_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 NULLresolves to that Edition’s default. Implemented once inapps/cms/src/server/utils/market-fallback.tsand 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.