@open-mercato/core 0.9.1-develop.7474.1.51db10479d → 0.9.1-develop.7475.1.28ca93ed97

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (597) hide show
  1. package/.turbo/turbo-build.log +2 -2
  2. package/dist/generated/entities/catalog_price_history_entry/index.js +53 -0
  3. package/dist/generated/entities/catalog_price_history_entry/index.js.map +7 -0
  4. package/dist/generated/entities/customer_group_terms/index.js +2 -0
  5. package/dist/generated/entities/customer_group_terms/index.js.map +2 -2
  6. package/dist/generated/entities/ecommerce_store/index.js +33 -0
  7. package/dist/generated/entities/ecommerce_store/index.js.map +7 -0
  8. package/dist/generated/entities/ecommerce_store_channel_binding/index.js +29 -0
  9. package/dist/generated/entities/ecommerce_store_channel_binding/index.js.map +7 -0
  10. package/dist/generated/entities/ecommerce_store_domain_binding/index.js +23 -0
  11. package/dist/generated/entities/ecommerce_store_domain_binding/index.js.map +7 -0
  12. package/dist/generated/entities.ids.generated.js +8 -1
  13. package/dist/generated/entities.ids.generated.js.map +2 -2
  14. package/dist/generated/entity-fields-registry.js +72 -0
  15. package/dist/generated/entity-fields-registry.js.map +2 -2
  16. package/dist/helpers/integration/customerGroupsFixtures.js.map +2 -2
  17. package/dist/modules/catalog/acl.js +8 -1
  18. package/dist/modules/catalog/acl.js.map +2 -2
  19. package/dist/modules/catalog/api/config/omnibus/route.js +184 -0
  20. package/dist/modules/catalog/api/config/omnibus/route.js.map +7 -0
  21. package/dist/modules/catalog/api/prices/history/route.js +131 -0
  22. package/dist/modules/catalog/api/prices/history/route.js.map +7 -0
  23. package/dist/modules/catalog/api/prices/omnibus-preview/route.js +184 -0
  24. package/dist/modules/catalog/api/prices/omnibus-preview/route.js.map +7 -0
  25. package/dist/modules/catalog/api/products/route.js +54 -245
  26. package/dist/modules/catalog/api/products/route.js.map +3 -3
  27. package/dist/modules/catalog/api/tags/route.js +6 -0
  28. package/dist/modules/catalog/api/tags/route.js.map +2 -2
  29. package/dist/modules/catalog/backend/config/catalog/page.js +3 -1
  30. package/dist/modules/catalog/backend/config/catalog/page.js.map +2 -2
  31. package/dist/modules/catalog/cli.js +80 -2
  32. package/dist/modules/catalog/cli.js.map +2 -2
  33. package/dist/modules/catalog/commands/categories.js +51 -2
  34. package/dist/modules/catalog/commands/categories.js.map +2 -2
  35. package/dist/modules/catalog/commands/prices.js +43 -1
  36. package/dist/modules/catalog/commands/prices.js.map +2 -2
  37. package/dist/modules/catalog/commands/products.js +71 -0
  38. package/dist/modules/catalog/commands/products.js.map +2 -2
  39. package/dist/modules/catalog/commands/variants.js +7 -0
  40. package/dist/modules/catalog/commands/variants.js.map +2 -2
  41. package/dist/modules/catalog/components/OmnibusSettings.js +703 -0
  42. package/dist/modules/catalog/components/OmnibusSettings.js.map +7 -0
  43. package/dist/modules/catalog/components/PriceEditorOmnibusRow.js +152 -0
  44. package/dist/modules/catalog/components/PriceEditorOmnibusRow.js.map +7 -0
  45. package/dist/modules/catalog/components/lookupSources.js +31 -0
  46. package/dist/modules/catalog/components/lookupSources.js.map +7 -0
  47. package/dist/modules/catalog/components/prices/PriceScopeSelectors.js +8 -2
  48. package/dist/modules/catalog/components/prices/PriceScopeSelectors.js.map +2 -2
  49. package/dist/modules/catalog/components/products/VariantBuilder.js +11 -1
  50. package/dist/modules/catalog/components/products/VariantBuilder.js.map +2 -2
  51. package/dist/modules/catalog/data/entities.js +122 -1
  52. package/dist/modules/catalog/data/entities.js.map +2 -2
  53. package/dist/modules/catalog/data/validators.js +78 -0
  54. package/dist/modules/catalog/data/validators.js.map +2 -2
  55. package/dist/modules/catalog/di.js +18 -0
  56. package/dist/modules/catalog/di.js.map +2 -2
  57. package/dist/modules/catalog/lib/omnibus.js +285 -0
  58. package/dist/modules/catalog/lib/omnibus.js.map +7 -0
  59. package/dist/modules/catalog/lib/omnibusBackfill.js +146 -0
  60. package/dist/modules/catalog/lib/omnibusBackfill.js.map +7 -0
  61. package/dist/modules/catalog/lib/omnibusCache.js +89 -0
  62. package/dist/modules/catalog/lib/omnibusCache.js.map +7 -0
  63. package/dist/modules/catalog/lib/omnibusConfig.js +64 -0
  64. package/dist/modules/catalog/lib/omnibusConfig.js.map +7 -0
  65. package/dist/modules/catalog/lib/omnibusHistoryQueries.js +191 -0
  66. package/dist/modules/catalog/lib/omnibusHistoryQueries.js.map +7 -0
  67. package/dist/modules/catalog/lib/omnibusPresentedEntry.js +73 -0
  68. package/dist/modules/catalog/lib/omnibusPresentedEntry.js.map +7 -0
  69. package/dist/modules/catalog/lib/omnibusProductListEnrichment.js +94 -0
  70. package/dist/modules/catalog/lib/omnibusProductListEnrichment.js.map +7 -0
  71. package/dist/modules/catalog/lib/omnibusTypes.js +115 -0
  72. package/dist/modules/catalog/lib/omnibusTypes.js.map +7 -0
  73. package/dist/modules/catalog/lib/optionSchemaTranslations.js +55 -0
  74. package/dist/modules/catalog/lib/optionSchemaTranslations.js.map +7 -0
  75. package/dist/modules/catalog/lib/priceHistoryQuery.js +103 -0
  76. package/dist/modules/catalog/lib/priceHistoryQuery.js.map +7 -0
  77. package/dist/modules/catalog/lib/pricing.js +26 -2
  78. package/dist/modules/catalog/lib/pricing.js.map +2 -2
  79. package/dist/modules/catalog/lib/productFilters.js +273 -0
  80. package/dist/modules/catalog/lib/productFilters.js.map +7 -0
  81. package/dist/modules/catalog/lib/productScopeKeys.js +105 -0
  82. package/dist/modules/catalog/lib/productScopeKeys.js.map +7 -0
  83. package/dist/modules/catalog/lib/productScopeReindex.js +73 -0
  84. package/dist/modules/catalog/lib/productScopeReindex.js.map +7 -0
  85. package/dist/modules/catalog/migrations/Migration20261005105352_catalog.js +39 -0
  86. package/dist/modules/catalog/migrations/Migration20261005105352_catalog.js.map +7 -0
  87. package/dist/modules/catalog/migrations/Migration20261005150824_catalog.js +39 -0
  88. package/dist/modules/catalog/migrations/Migration20261005150824_catalog.js.map +7 -0
  89. package/dist/modules/catalog/services/catalogOmnibusService.js +480 -0
  90. package/dist/modules/catalog/services/catalogOmnibusService.js.map +7 -0
  91. package/dist/modules/catalog/setup.js +3 -1
  92. package/dist/modules/catalog/setup.js.map +2 -2
  93. package/dist/modules/catalog/subscribers/product-scope-keys-category-deleted.js +16 -0
  94. package/dist/modules/catalog/subscribers/product-scope-keys-category-deleted.js.map +7 -0
  95. package/dist/modules/catalog/subscribers/product-scope-keys-category-updated.js +17 -0
  96. package/dist/modules/catalog/subscribers/product-scope-keys-category-updated.js.map +7 -0
  97. package/dist/modules/catalog/translations.js +10 -0
  98. package/dist/modules/catalog/translations.js.map +2 -2
  99. package/dist/modules/configs/lib/upgrade-actions.js +28 -0
  100. package/dist/modules/configs/lib/upgrade-actions.js.map +2 -2
  101. package/dist/modules/customer_groups/api/customer-groups/[id]/terms/route.js +5 -0
  102. package/dist/modules/customer_groups/api/customer-groups/[id]/terms/route.js.map +2 -2
  103. package/dist/modules/customer_groups/components/CustomerGroupTermsSection.js +111 -3
  104. package/dist/modules/customer_groups/components/CustomerGroupTermsSection.js.map +2 -2
  105. package/dist/modules/customer_groups/components/customerGroupAssortmentScope.js +43 -0
  106. package/dist/modules/customer_groups/components/customerGroupAssortmentScope.js.map +7 -0
  107. package/dist/modules/customer_groups/data/entities.js +3 -0
  108. package/dist/modules/customer_groups/data/entities.js.map +2 -2
  109. package/dist/modules/customer_groups/data/validators.js +9 -0
  110. package/dist/modules/customer_groups/data/validators.js.map +2 -2
  111. package/dist/modules/customer_groups/migrations/Migration20261005110250_customer_groups.js +17 -0
  112. package/dist/modules/customer_groups/migrations/Migration20261005110250_customer_groups.js.map +7 -0
  113. package/dist/modules/customer_groups/services/customerGroupsService.js +62 -15
  114. package/dist/modules/customer_groups/services/customerGroupsService.js.map +2 -2
  115. package/dist/modules/ecommerce/acl.js +13 -0
  116. package/dist/modules/ecommerce/acl.js.map +7 -0
  117. package/dist/modules/ecommerce/api/domain-mappings/route.js +72 -0
  118. package/dist/modules/ecommerce/api/domain-mappings/route.js.map +7 -0
  119. package/dist/modules/ecommerce/api/openapi.js +27 -0
  120. package/dist/modules/ecommerce/api/openapi.js.map +7 -0
  121. package/dist/modules/ecommerce/api/store-channel-bindings/[id]/assortment-count/route.js +109 -0
  122. package/dist/modules/ecommerce/api/store-channel-bindings/[id]/assortment-count/route.js.map +7 -0
  123. package/dist/modules/ecommerce/api/store-channel-bindings/crud.js +265 -0
  124. package/dist/modules/ecommerce/api/store-channel-bindings/crud.js.map +7 -0
  125. package/dist/modules/ecommerce/api/store-channel-bindings/route.js +67 -0
  126. package/dist/modules/ecommerce/api/store-channel-bindings/route.js.map +7 -0
  127. package/dist/modules/ecommerce/api/store-domain-bindings/crud.js +296 -0
  128. package/dist/modules/ecommerce/api/store-domain-bindings/crud.js.map +7 -0
  129. package/dist/modules/ecommerce/api/store-domain-bindings/route.js +73 -0
  130. package/dist/modules/ecommerce/api/store-domain-bindings/route.js.map +7 -0
  131. package/dist/modules/ecommerce/api/storefront/categories/[slug]/route.js +87 -0
  132. package/dist/modules/ecommerce/api/storefront/categories/[slug]/route.js.map +7 -0
  133. package/dist/modules/ecommerce/api/storefront/categories/openapiSchemas.js +50 -0
  134. package/dist/modules/ecommerce/api/storefront/categories/openapiSchemas.js.map +7 -0
  135. package/dist/modules/ecommerce/api/storefront/categories/route.js +75 -0
  136. package/dist/modules/ecommerce/api/storefront/categories/route.js.map +7 -0
  137. package/dist/modules/ecommerce/api/storefront/context/route.js +163 -0
  138. package/dist/modules/ecommerce/api/storefront/context/route.js.map +7 -0
  139. package/dist/modules/ecommerce/api/storefront/products/[idOrHandle]/route.js +87 -0
  140. package/dist/modules/ecommerce/api/storefront/products/[idOrHandle]/route.js.map +7 -0
  141. package/dist/modules/ecommerce/api/storefront/products/openapiSchemas.js +177 -0
  142. package/dist/modules/ecommerce/api/storefront/products/openapiSchemas.js.map +7 -0
  143. package/dist/modules/ecommerce/api/storefront/products/route.js +78 -0
  144. package/dist/modules/ecommerce/api/storefront/products/route.js.map +7 -0
  145. package/dist/modules/ecommerce/api/storefront/search/suggest/openapiSchemas.js +25 -0
  146. package/dist/modules/ecommerce/api/storefront/search/suggest/openapiSchemas.js.map +7 -0
  147. package/dist/modules/ecommerce/api/storefront/search/suggest/route.js +75 -0
  148. package/dist/modules/ecommerce/api/storefront/search/suggest/route.js.map +7 -0
  149. package/dist/modules/ecommerce/api/storefront/storefrontRouteSupport.js +58 -0
  150. package/dist/modules/ecommerce/api/storefront/storefrontRouteSupport.js.map +7 -0
  151. package/dist/modules/ecommerce/api/stores/[id]/branding/route.js +183 -0
  152. package/dist/modules/ecommerce/api/stores/[id]/branding/route.js.map +7 -0
  153. package/dist/modules/ecommerce/api/stores/[id]/preview-branding/route.js +92 -0
  154. package/dist/modules/ecommerce/api/stores/[id]/preview-branding/route.js.map +7 -0
  155. package/dist/modules/ecommerce/api/stores/crud.js +401 -0
  156. package/dist/modules/ecommerce/api/stores/crud.js.map +7 -0
  157. package/dist/modules/ecommerce/api/stores/route.js +64 -0
  158. package/dist/modules/ecommerce/api/stores/route.js.map +7 -0
  159. package/dist/modules/ecommerce/backend/config/ecommerce/[id]/page.js +9 -0
  160. package/dist/modules/ecommerce/backend/config/ecommerce/[id]/page.js.map +7 -0
  161. package/dist/modules/ecommerce/backend/config/ecommerce/[id]/page.meta.js +16 -0
  162. package/dist/modules/ecommerce/backend/config/ecommerce/[id]/page.meta.js.map +7 -0
  163. package/dist/modules/ecommerce/backend/config/ecommerce/page.js +9 -0
  164. package/dist/modules/ecommerce/backend/config/ecommerce/page.js.map +7 -0
  165. package/dist/modules/ecommerce/backend/config/ecommerce/page.meta.js +16 -0
  166. package/dist/modules/ecommerce/backend/config/ecommerce/page.meta.js.map +7 -0
  167. package/dist/modules/ecommerce/commands/stores.js +110 -0
  168. package/dist/modules/ecommerce/commands/stores.js.map +7 -0
  169. package/dist/modules/ecommerce/components/ChannelAssortmentCount.js +90 -0
  170. package/dist/modules/ecommerce/components/ChannelAssortmentCount.js.map +7 -0
  171. package/dist/modules/ecommerce/components/ChannelBindingFormSections.js +33 -0
  172. package/dist/modules/ecommerce/components/ChannelBindingFormSections.js.map +7 -0
  173. package/dist/modules/ecommerce/components/ChannelLookupPicker.js +11 -0
  174. package/dist/modules/ecommerce/components/ChannelLookupPicker.js.map +7 -0
  175. package/dist/modules/ecommerce/components/DomainStatusWarning.js +24 -0
  176. package/dist/modules/ecommerce/components/DomainStatusWarning.js.map +7 -0
  177. package/dist/modules/ecommerce/components/StoreAvailabilityDefaultsSection.js +190 -0
  178. package/dist/modules/ecommerce/components/StoreAvailabilityDefaultsSection.js.map +7 -0
  179. package/dist/modules/ecommerce/components/StoreBrandingControls.js +67 -0
  180. package/dist/modules/ecommerce/components/StoreBrandingControls.js.map +7 -0
  181. package/dist/modules/ecommerce/components/StoreBrandingPreview.js +69 -0
  182. package/dist/modules/ecommerce/components/StoreBrandingPreview.js.map +7 -0
  183. package/dist/modules/ecommerce/components/StoreBrandingTab.js +218 -0
  184. package/dist/modules/ecommerce/components/StoreBrandingTab.js.map +7 -0
  185. package/dist/modules/ecommerce/components/StoreChannelBindingDialog.js +270 -0
  186. package/dist/modules/ecommerce/components/StoreChannelBindingDialog.js.map +7 -0
  187. package/dist/modules/ecommerce/components/StoreChannelsTab.js +313 -0
  188. package/dist/modules/ecommerce/components/StoreChannelsTab.js.map +7 -0
  189. package/dist/modules/ecommerce/components/StoreCreateDialog.js +164 -0
  190. package/dist/modules/ecommerce/components/StoreCreateDialog.js.map +7 -0
  191. package/dist/modules/ecommerce/components/StoreDomainBindingDialog.js +162 -0
  192. package/dist/modules/ecommerce/components/StoreDomainBindingDialog.js.map +7 -0
  193. package/dist/modules/ecommerce/components/StoreDomainsTab.js +357 -0
  194. package/dist/modules/ecommerce/components/StoreDomainsTab.js.map +7 -0
  195. package/dist/modules/ecommerce/components/StoreEditPage.js +155 -0
  196. package/dist/modules/ecommerce/components/StoreEditPage.js.map +7 -0
  197. package/dist/modules/ecommerce/components/StoreGeneralTab.js +167 -0
  198. package/dist/modules/ecommerce/components/StoreGeneralTab.js.map +7 -0
  199. package/dist/modules/ecommerce/components/StoreListPage.js +290 -0
  200. package/dist/modules/ecommerce/components/StoreListPage.js.map +7 -0
  201. package/dist/modules/ecommerce/components/StoreSeoTab.js +132 -0
  202. package/dist/modules/ecommerce/components/StoreSeoTab.js.map +7 -0
  203. package/dist/modules/ecommerce/components/StoreStatusBadge.js +20 -0
  204. package/dist/modules/ecommerce/components/StoreStatusBadge.js.map +7 -0
  205. package/dist/modules/ecommerce/components/channelLookups.js +28 -0
  206. package/dist/modules/ecommerce/components/channelLookups.js.map +7 -0
  207. package/dist/modules/ecommerce/components/storeAdmin.js +41 -0
  208. package/dist/modules/ecommerce/components/storeAdmin.js.map +7 -0
  209. package/dist/modules/ecommerce/components/storeBrandingForm.js +197 -0
  210. package/dist/modules/ecommerce/components/storeBrandingForm.js.map +7 -0
  211. package/dist/modules/ecommerce/components/storeChannels.js +140 -0
  212. package/dist/modules/ecommerce/components/storeChannels.js.map +7 -0
  213. package/dist/modules/ecommerce/components/storeDomains.js +167 -0
  214. package/dist/modules/ecommerce/components/storeDomains.js.map +7 -0
  215. package/dist/modules/ecommerce/components/storeEditTabs.js +62 -0
  216. package/dist/modules/ecommerce/components/storeEditTabs.js.map +7 -0
  217. package/dist/modules/ecommerce/components/storeGeneral.js +95 -0
  218. package/dist/modules/ecommerce/components/storeGeneral.js.map +7 -0
  219. package/dist/modules/ecommerce/components/storeSeo.js +69 -0
  220. package/dist/modules/ecommerce/components/storeSeo.js.map +7 -0
  221. package/dist/modules/ecommerce/components/useChannelAssortmentCount.js +45 -0
  222. package/dist/modules/ecommerce/components/useChannelAssortmentCount.js.map +7 -0
  223. package/dist/modules/ecommerce/components/useStoreAccess.js +28 -0
  224. package/dist/modules/ecommerce/components/useStoreAccess.js.map +7 -0
  225. package/dist/modules/ecommerce/data/enrichers.js +157 -0
  226. package/dist/modules/ecommerce/data/enrichers.js.map +7 -0
  227. package/dist/modules/ecommerce/data/entities.js +197 -0
  228. package/dist/modules/ecommerce/data/entities.js.map +7 -0
  229. package/dist/modules/ecommerce/data/validators.js +406 -0
  230. package/dist/modules/ecommerce/data/validators.js.map +7 -0
  231. package/dist/modules/ecommerce/di.js +11 -0
  232. package/dist/modules/ecommerce/di.js.map +7 -0
  233. package/dist/modules/ecommerce/events.js +27 -0
  234. package/dist/modules/ecommerce/events.js.map +7 -0
  235. package/dist/modules/ecommerce/index.js +16 -0
  236. package/dist/modules/ecommerce/index.js.map +7 -0
  237. package/dist/modules/ecommerce/lib/assortmentCount.js +112 -0
  238. package/dist/modules/ecommerce/lib/assortmentCount.js.map +7 -0
  239. package/dist/modules/ecommerce/lib/brandingStyles.js +184 -0
  240. package/dist/modules/ecommerce/lib/brandingStyles.js.map +7 -0
  241. package/dist/modules/ecommerce/lib/buyerContext.js +284 -0
  242. package/dist/modules/ecommerce/lib/buyerContext.js.map +7 -0
  243. package/dist/modules/ecommerce/lib/cacheInvalidation.js +186 -0
  244. package/dist/modules/ecommerce/lib/cacheInvalidation.js.map +7 -0
  245. package/dist/modules/ecommerce/lib/cacheKeys.js +236 -0
  246. package/dist/modules/ecommerce/lib/cacheKeys.js.map +7 -0
  247. package/dist/modules/ecommerce/lib/createConflictRecheck.js +29 -0
  248. package/dist/modules/ecommerce/lib/createConflictRecheck.js.map +7 -0
  249. package/dist/modules/ecommerce/lib/crudEvents.js +94 -0
  250. package/dist/modules/ecommerce/lib/crudEvents.js.map +7 -0
  251. package/dist/modules/ecommerce/lib/crudSupport.js +64 -0
  252. package/dist/modules/ecommerce/lib/crudSupport.js.map +7 -0
  253. package/dist/modules/ecommerce/lib/domainBindingRebind.js +99 -0
  254. package/dist/modules/ecommerce/lib/domainBindingRebind.js.map +7 -0
  255. package/dist/modules/ecommerce/lib/domainMappingSummaries.js +39 -0
  256. package/dist/modules/ecommerce/lib/domainMappingSummaries.js.map +7 -0
  257. package/dist/modules/ecommerce/lib/exclusiveFlag.js +57 -0
  258. package/dist/modules/ecommerce/lib/exclusiveFlag.js.map +7 -0
  259. package/dist/modules/ecommerce/lib/references.js +70 -0
  260. package/dist/modules/ecommerce/lib/references.js.map +7 -0
  261. package/dist/modules/ecommerce/lib/seedDraftStore.js +99 -0
  262. package/dist/modules/ecommerce/lib/seedDraftStore.js.map +7 -0
  263. package/dist/modules/ecommerce/lib/storeBindingCascade.js +109 -0
  264. package/dist/modules/ecommerce/lib/storeBindingCascade.js.map +7 -0
  265. package/dist/modules/ecommerce/lib/storeBranding.js +40 -0
  266. package/dist/modules/ecommerce/lib/storeBranding.js.map +7 -0
  267. package/dist/modules/ecommerce/lib/storeBrandingRoute.js +76 -0
  268. package/dist/modules/ecommerce/lib/storeBrandingRoute.js.map +7 -0
  269. package/dist/modules/ecommerce/lib/storeContext.js +393 -0
  270. package/dist/modules/ecommerce/lib/storeContext.js.map +7 -0
  271. package/dist/modules/ecommerce/lib/storeContextService.js +43 -0
  272. package/dist/modules/ecommerce/lib/storeContextService.js.map +7 -0
  273. package/dist/modules/ecommerce/lib/storeLocale.js +60 -0
  274. package/dist/modules/ecommerce/lib/storeLocale.js.map +7 -0
  275. package/dist/modules/ecommerce/lib/storeNotifications.js +41 -0
  276. package/dist/modules/ecommerce/lib/storeNotifications.js.map +7 -0
  277. package/dist/modules/ecommerce/lib/storefrontCatalogSupport.js +248 -0
  278. package/dist/modules/ecommerce/lib/storefrontCatalogSupport.js.map +7 -0
  279. package/dist/modules/ecommerce/lib/storefrontCategories.js +241 -0
  280. package/dist/modules/ecommerce/lib/storefrontCategories.js.map +7 -0
  281. package/dist/modules/ecommerce/lib/storefrontCategoryCache.js +71 -0
  282. package/dist/modules/ecommerce/lib/storefrontCategoryCache.js.map +7 -0
  283. package/dist/modules/ecommerce/lib/storefrontDetail.js +447 -0
  284. package/dist/modules/ecommerce/lib/storefrontDetail.js.map +7 -0
  285. package/dist/modules/ecommerce/lib/storefrontFacets.js +441 -0
  286. package/dist/modules/ecommerce/lib/storefrontFacets.js.map +7 -0
  287. package/dist/modules/ecommerce/lib/storefrontOptionFilter.js +15 -0
  288. package/dist/modules/ecommerce/lib/storefrontOptionFilter.js.map +7 -0
  289. package/dist/modules/ecommerce/lib/storefrontPricing.js +393 -0
  290. package/dist/modules/ecommerce/lib/storefrontPricing.js.map +7 -0
  291. package/dist/modules/ecommerce/lib/storefrontProductCache.js +90 -0
  292. package/dist/modules/ecommerce/lib/storefrontProductCache.js.map +7 -0
  293. package/dist/modules/ecommerce/lib/storefrontProductScope.js +107 -0
  294. package/dist/modules/ecommerce/lib/storefrontProductScope.js.map +7 -0
  295. package/dist/modules/ecommerce/lib/storefrontProducts.js +670 -0
  296. package/dist/modules/ecommerce/lib/storefrontProducts.js.map +7 -0
  297. package/dist/modules/ecommerce/lib/storefrontQuery.js +156 -0
  298. package/dist/modules/ecommerce/lib/storefrontQuery.js.map +7 -0
  299. package/dist/modules/ecommerce/lib/storefrontRateLimit.js +99 -0
  300. package/dist/modules/ecommerce/lib/storefrontRateLimit.js.map +7 -0
  301. package/dist/modules/ecommerce/lib/storefrontSearch.js +303 -0
  302. package/dist/modules/ecommerce/lib/storefrontSearch.js.map +7 -0
  303. package/dist/modules/ecommerce/lib/storefrontSearchCache.js +35 -0
  304. package/dist/modules/ecommerce/lib/storefrontSearchCache.js.map +7 -0
  305. package/dist/modules/ecommerce/lib/subscriberSupport.js +33 -0
  306. package/dist/modules/ecommerce/lib/subscriberSupport.js.map +7 -0
  307. package/dist/modules/ecommerce/lib/types.js +1 -0
  308. package/dist/modules/ecommerce/lib/types.js.map +7 -0
  309. package/dist/modules/ecommerce/migrations/Migration20261005113233_ecommerce.js +32 -0
  310. package/dist/modules/ecommerce/migrations/Migration20261005113233_ecommerce.js.map +7 -0
  311. package/dist/modules/ecommerce/migrations/Migration20261006112733_ecommerce.js +17 -0
  312. package/dist/modules/ecommerce/migrations/Migration20261006112733_ecommerce.js.map +7 -0
  313. package/dist/modules/ecommerce/notifications.client.js +47 -0
  314. package/dist/modules/ecommerce/notifications.client.js.map +7 -0
  315. package/dist/modules/ecommerce/notifications.js +48 -0
  316. package/dist/modules/ecommerce/notifications.js.map +7 -0
  317. package/dist/modules/ecommerce/search.js +83 -0
  318. package/dist/modules/ecommerce/search.js.map +7 -0
  319. package/dist/modules/ecommerce/setup.js +17 -0
  320. package/dist/modules/ecommerce/setup.js.map +7 -0
  321. package/dist/modules/ecommerce/subscribers/assortment-empty-notifications.js +14 -0
  322. package/dist/modules/ecommerce/subscribers/assortment-empty-notifications.js.map +7 -0
  323. package/dist/modules/ecommerce/subscribers/availability-policy-cache-invalidation.js +14 -0
  324. package/dist/modules/ecommerce/subscribers/availability-policy-cache-invalidation.js.map +7 -0
  325. package/dist/modules/ecommerce/subscribers/catalog-category-cache-invalidation.js +14 -0
  326. package/dist/modules/ecommerce/subscribers/catalog-category-cache-invalidation.js.map +7 -0
  327. package/dist/modules/ecommerce/subscribers/catalog-price-cache-invalidation.js +14 -0
  328. package/dist/modules/ecommerce/subscribers/catalog-price-cache-invalidation.js.map +7 -0
  329. package/dist/modules/ecommerce/subscribers/catalog-product-cache-invalidation.js +14 -0
  330. package/dist/modules/ecommerce/subscribers/catalog-product-cache-invalidation.js.map +7 -0
  331. package/dist/modules/ecommerce/subscribers/catalog-variant-cache-invalidation.js +14 -0
  332. package/dist/modules/ecommerce/subscribers/catalog-variant-cache-invalidation.js.map +7 -0
  333. package/dist/modules/ecommerce/subscribers/customer-group-cache-invalidation.js +14 -0
  334. package/dist/modules/ecommerce/subscribers/customer-group-cache-invalidation.js.map +7 -0
  335. package/dist/modules/ecommerce/subscribers/customer-group-membership-cache-invalidation.js +14 -0
  336. package/dist/modules/ecommerce/subscribers/customer-group-membership-cache-invalidation.js.map +7 -0
  337. package/dist/modules/ecommerce/subscribers/customer-group-terms-cache-invalidation.js +14 -0
  338. package/dist/modules/ecommerce/subscribers/customer-group-terms-cache-invalidation.js.map +7 -0
  339. package/dist/modules/ecommerce/subscribers/customer-user-deleted-cache-invalidation.js +14 -0
  340. package/dist/modules/ecommerce/subscribers/customer-user-deleted-cache-invalidation.js.map +7 -0
  341. package/dist/modules/ecommerce/subscribers/customer-user-updated-cache-invalidation.js +14 -0
  342. package/dist/modules/ecommerce/subscribers/customer-user-updated-cache-invalidation.js.map +7 -0
  343. package/dist/modules/ecommerce/subscribers/domain-binding-rebind.js +16 -0
  344. package/dist/modules/ecommerce/subscribers/domain-binding-rebind.js.map +7 -0
  345. package/dist/modules/ecommerce/subscribers/domain-mapping-cache-invalidation.js +14 -0
  346. package/dist/modules/ecommerce/subscribers/domain-mapping-cache-invalidation.js.map +7 -0
  347. package/dist/modules/ecommerce/subscribers/store-cache-invalidation.js +14 -0
  348. package/dist/modules/ecommerce/subscribers/store-cache-invalidation.js.map +7 -0
  349. package/dist/modules/ecommerce/subscribers/store-channel-binding-cache-invalidation.js +14 -0
  350. package/dist/modules/ecommerce/subscribers/store-channel-binding-cache-invalidation.js.map +7 -0
  351. package/dist/modules/ecommerce/subscribers/store-domain-binding-cache-invalidation.js +14 -0
  352. package/dist/modules/ecommerce/subscribers/store-domain-binding-cache-invalidation.js.map +7 -0
  353. package/dist/modules/ecommerce/subscribers/store-notifications.js +14 -0
  354. package/dist/modules/ecommerce/subscribers/store-notifications.js.map +7 -0
  355. package/dist/modules/query_index/lib/batch.js +23 -9
  356. package/dist/modules/query_index/lib/batch.js.map +2 -2
  357. package/dist/modules/query_index/lib/doc-enrichers.js +212 -0
  358. package/dist/modules/query_index/lib/doc-enrichers.js.map +7 -0
  359. package/dist/modules/query_index/lib/document.js +2 -0
  360. package/dist/modules/query_index/lib/document.js.map +2 -2
  361. package/dist/modules/query_index/lib/engine.js +68 -1
  362. package/dist/modules/query_index/lib/engine.js.map +2 -2
  363. package/dist/modules/query_index/lib/indexer.js +7 -0
  364. package/dist/modules/query_index/lib/indexer.js.map +2 -2
  365. package/dist/modules/query_index/lib/search-tokens.js +2 -0
  366. package/dist/modules/query_index/lib/search-tokens.js.map +2 -2
  367. package/dist/modules/query_index/migrations/Migration20261005201500_query_index.js +17 -0
  368. package/dist/modules/query_index/migrations/Migration20261005201500_query_index.js.map +7 -0
  369. package/dist/modules/translations/components/TranslationManager.js +3 -3
  370. package/dist/modules/translations/components/TranslationManager.js.map +2 -2
  371. package/dist/modules/translations/data/validators.js +1 -1
  372. package/dist/modules/translations/data/validators.js.map +1 -1
  373. package/dist/modules/translations/lib/batch.js +21 -1
  374. package/dist/modules/translations/lib/batch.js.map +2 -2
  375. package/dist/modules/translations/lib/helpers.js +3 -0
  376. package/dist/modules/translations/lib/helpers.js.map +2 -2
  377. package/dist/modules/translations/lib/resolve-field-list.js +14 -2
  378. package/dist/modules/translations/lib/resolve-field-list.js.map +2 -2
  379. package/generated/entities/catalog_price_history_entry/index.ts +25 -0
  380. package/generated/entities/customer_group_terms/index.ts +1 -0
  381. package/generated/entities/ecommerce_store/index.ts +15 -0
  382. package/generated/entities/ecommerce_store_channel_binding/index.ts +13 -0
  383. package/generated/entities/ecommerce_store_domain_binding/index.ts +10 -0
  384. package/generated/entities.ids.generated.ts +8 -1
  385. package/generated/entity-fields-registry.ts +72 -0
  386. package/package.json +7 -7
  387. package/src/helpers/integration/customerGroupsFixtures.ts +2 -0
  388. package/src/modules/auth/i18n/de.json +8 -0
  389. package/src/modules/auth/i18n/en.json +8 -0
  390. package/src/modules/auth/i18n/es.json +8 -0
  391. package/src/modules/auth/i18n/ko.json +8 -0
  392. package/src/modules/auth/i18n/pl.json +8 -0
  393. package/src/modules/catalog/AGENTS.md +5 -2
  394. package/src/modules/catalog/acl.ts +7 -0
  395. package/src/modules/catalog/api/config/omnibus/route.ts +204 -0
  396. package/src/modules/catalog/api/prices/history/route.ts +139 -0
  397. package/src/modules/catalog/api/prices/omnibus-preview/route.ts +202 -0
  398. package/src/modules/catalog/api/products/route.ts +53 -308
  399. package/src/modules/catalog/api/tags/route.ts +6 -0
  400. package/src/modules/catalog/backend/config/catalog/page.tsx +2 -0
  401. package/src/modules/catalog/cli.ts +84 -1
  402. package/src/modules/catalog/commands/categories.ts +54 -2
  403. package/src/modules/catalog/commands/prices.ts +43 -1
  404. package/src/modules/catalog/commands/products.ts +84 -1
  405. package/src/modules/catalog/commands/variants.ts +7 -0
  406. package/src/modules/catalog/components/OmnibusSettings.tsx +832 -0
  407. package/src/modules/catalog/components/PriceEditorOmnibusRow.tsx +219 -0
  408. package/src/modules/catalog/components/lookupSources.ts +29 -0
  409. package/src/modules/catalog/components/prices/PriceScopeSelectors.tsx +9 -0
  410. package/src/modules/catalog/components/products/VariantBuilder.tsx +9 -0
  411. package/src/modules/catalog/data/entities.ts +123 -1
  412. package/src/modules/catalog/data/validators.ts +118 -0
  413. package/src/modules/catalog/di.ts +23 -0
  414. package/src/modules/catalog/i18n/de.json +77 -0
  415. package/src/modules/catalog/i18n/en.json +77 -0
  416. package/src/modules/catalog/i18n/es.json +77 -0
  417. package/src/modules/catalog/i18n/ko.json +77 -0
  418. package/src/modules/catalog/i18n/pl.json +77 -0
  419. package/src/modules/catalog/lib/omnibus.ts +406 -0
  420. package/src/modules/catalog/lib/omnibusBackfill.ts +198 -0
  421. package/src/modules/catalog/lib/omnibusCache.ts +111 -0
  422. package/src/modules/catalog/lib/omnibusConfig.ts +73 -0
  423. package/src/modules/catalog/lib/omnibusHistoryQueries.ts +248 -0
  424. package/src/modules/catalog/lib/omnibusPresentedEntry.ts +76 -0
  425. package/src/modules/catalog/lib/omnibusProductListEnrichment.ts +125 -0
  426. package/src/modules/catalog/lib/omnibusTypes.ts +202 -0
  427. package/src/modules/catalog/lib/optionSchemaTranslations.ts +55 -0
  428. package/src/modules/catalog/lib/priceHistoryQuery.ts +155 -0
  429. package/src/modules/catalog/lib/pricing.ts +42 -4
  430. package/src/modules/catalog/lib/productFilters.ts +349 -0
  431. package/src/modules/catalog/lib/productScopeKeys.ts +162 -0
  432. package/src/modules/catalog/lib/productScopeReindex.ts +139 -0
  433. package/src/modules/catalog/migrations/.snapshot-open-mercato.json +572 -0
  434. package/src/modules/catalog/migrations/Migration20261005105352_catalog.ts +39 -0
  435. package/src/modules/catalog/migrations/Migration20261005150824_catalog.ts +39 -0
  436. package/src/modules/catalog/services/catalogOmnibusService.ts +642 -0
  437. package/src/modules/catalog/setup.ts +2 -0
  438. package/src/modules/catalog/subscribers/product-scope-keys-category-deleted.ts +18 -0
  439. package/src/modules/catalog/subscribers/product-scope-keys-category-updated.ts +19 -0
  440. package/src/modules/catalog/translations.ts +12 -0
  441. package/src/modules/configs/i18n/de.json +5 -1
  442. package/src/modules/configs/i18n/en.json +5 -1
  443. package/src/modules/configs/i18n/es.json +5 -1
  444. package/src/modules/configs/i18n/ko.json +5 -1
  445. package/src/modules/configs/i18n/pl.json +5 -1
  446. package/src/modules/configs/lib/upgrade-actions.ts +35 -0
  447. package/src/modules/customer_groups/api/customer-groups/[id]/terms/route.ts +7 -0
  448. package/src/modules/customer_groups/components/CustomerGroupTermsSection.tsx +124 -2
  449. package/src/modules/customer_groups/components/customerGroupAssortmentScope.ts +46 -0
  450. package/src/modules/customer_groups/data/entities.ts +4 -0
  451. package/src/modules/customer_groups/data/validators.ts +14 -0
  452. package/src/modules/customer_groups/i18n/de.json +12 -0
  453. package/src/modules/customer_groups/i18n/en.json +12 -0
  454. package/src/modules/customer_groups/i18n/es.json +12 -0
  455. package/src/modules/customer_groups/i18n/ko.json +12 -0
  456. package/src/modules/customer_groups/i18n/pl.json +12 -0
  457. package/src/modules/customer_groups/migrations/.snapshot-open-mercato.json +17 -0
  458. package/src/modules/customer_groups/migrations/Migration20261005110250_customer_groups.ts +15 -0
  459. package/src/modules/customer_groups/services/customerGroupsService.ts +74 -23
  460. package/src/modules/ecommerce/acl.ts +9 -0
  461. package/src/modules/ecommerce/api/domain-mappings/route.ts +76 -0
  462. package/src/modules/ecommerce/api/openapi.ts +28 -0
  463. package/src/modules/ecommerce/api/store-channel-bindings/[id]/assortment-count/route.ts +141 -0
  464. package/src/modules/ecommerce/api/store-channel-bindings/crud.ts +283 -0
  465. package/src/modules/ecommerce/api/store-channel-bindings/route.ts +67 -0
  466. package/src/modules/ecommerce/api/store-domain-bindings/crud.ts +324 -0
  467. package/src/modules/ecommerce/api/store-domain-bindings/route.ts +73 -0
  468. package/src/modules/ecommerce/api/storefront/categories/[slug]/route.ts +94 -0
  469. package/src/modules/ecommerce/api/storefront/categories/openapiSchemas.ts +63 -0
  470. package/src/modules/ecommerce/api/storefront/categories/route.ts +78 -0
  471. package/src/modules/ecommerce/api/storefront/context/route.ts +203 -0
  472. package/src/modules/ecommerce/api/storefront/products/[idOrHandle]/route.ts +94 -0
  473. package/src/modules/ecommerce/api/storefront/products/openapiSchemas.ts +193 -0
  474. package/src/modules/ecommerce/api/storefront/products/route.ts +81 -0
  475. package/src/modules/ecommerce/api/storefront/search/suggest/openapiSchemas.ts +25 -0
  476. package/src/modules/ecommerce/api/storefront/search/suggest/route.ts +78 -0
  477. package/src/modules/ecommerce/api/storefront/storefrontRouteSupport.ts +68 -0
  478. package/src/modules/ecommerce/api/stores/[id]/branding/route.ts +205 -0
  479. package/src/modules/ecommerce/api/stores/[id]/preview-branding/route.ts +99 -0
  480. package/src/modules/ecommerce/api/stores/crud.ts +457 -0
  481. package/src/modules/ecommerce/api/stores/route.ts +66 -0
  482. package/src/modules/ecommerce/backend/config/ecommerce/[id]/page.meta.ts +12 -0
  483. package/src/modules/ecommerce/backend/config/ecommerce/[id]/page.tsx +5 -0
  484. package/src/modules/ecommerce/backend/config/ecommerce/page.meta.ts +12 -0
  485. package/src/modules/ecommerce/backend/config/ecommerce/page.tsx +5 -0
  486. package/src/modules/ecommerce/commands/stores.ts +154 -0
  487. package/src/modules/ecommerce/components/ChannelAssortmentCount.tsx +138 -0
  488. package/src/modules/ecommerce/components/ChannelBindingFormSections.tsx +40 -0
  489. package/src/modules/ecommerce/components/ChannelLookupPicker.tsx +5 -0
  490. package/src/modules/ecommerce/components/DomainStatusWarning.tsx +32 -0
  491. package/src/modules/ecommerce/components/StoreAvailabilityDefaultsSection.tsx +219 -0
  492. package/src/modules/ecommerce/components/StoreBrandingControls.tsx +73 -0
  493. package/src/modules/ecommerce/components/StoreBrandingPreview.tsx +77 -0
  494. package/src/modules/ecommerce/components/StoreBrandingTab.tsx +242 -0
  495. package/src/modules/ecommerce/components/StoreChannelBindingDialog.tsx +306 -0
  496. package/src/modules/ecommerce/components/StoreChannelsTab.tsx +375 -0
  497. package/src/modules/ecommerce/components/StoreCreateDialog.tsx +204 -0
  498. package/src/modules/ecommerce/components/StoreDomainBindingDialog.tsx +198 -0
  499. package/src/modules/ecommerce/components/StoreDomainsTab.tsx +426 -0
  500. package/src/modules/ecommerce/components/StoreEditPage.tsx +193 -0
  501. package/src/modules/ecommerce/components/StoreGeneralTab.tsx +189 -0
  502. package/src/modules/ecommerce/components/StoreListPage.tsx +326 -0
  503. package/src/modules/ecommerce/components/StoreSeoTab.tsx +152 -0
  504. package/src/modules/ecommerce/components/StoreStatusBadge.tsx +22 -0
  505. package/src/modules/ecommerce/components/channelLookups.ts +24 -0
  506. package/src/modules/ecommerce/components/storeAdmin.ts +56 -0
  507. package/src/modules/ecommerce/components/storeBrandingForm.ts +230 -0
  508. package/src/modules/ecommerce/components/storeChannels.ts +210 -0
  509. package/src/modules/ecommerce/components/storeDomains.ts +221 -0
  510. package/src/modules/ecommerce/components/storeEditTabs.tsx +76 -0
  511. package/src/modules/ecommerce/components/storeGeneral.ts +155 -0
  512. package/src/modules/ecommerce/components/storeSeo.ts +78 -0
  513. package/src/modules/ecommerce/components/useChannelAssortmentCount.ts +67 -0
  514. package/src/modules/ecommerce/components/useStoreAccess.ts +36 -0
  515. package/src/modules/ecommerce/data/enrichers.ts +213 -0
  516. package/src/modules/ecommerce/data/entities.ts +168 -0
  517. package/src/modules/ecommerce/data/validators.ts +587 -0
  518. package/src/modules/ecommerce/di.ts +9 -0
  519. package/src/modules/ecommerce/events.ts +27 -0
  520. package/src/modules/ecommerce/i18n/de.json +341 -0
  521. package/src/modules/ecommerce/i18n/en.json +341 -0
  522. package/src/modules/ecommerce/i18n/es.json +341 -0
  523. package/src/modules/ecommerce/i18n/ko.json +341 -0
  524. package/src/modules/ecommerce/i18n/pl.json +341 -0
  525. package/src/modules/ecommerce/index.ts +14 -0
  526. package/src/modules/ecommerce/lib/assortmentCount.ts +175 -0
  527. package/src/modules/ecommerce/lib/brandingStyles.ts +215 -0
  528. package/src/modules/ecommerce/lib/buyerContext.ts +401 -0
  529. package/src/modules/ecommerce/lib/cacheInvalidation.ts +302 -0
  530. package/src/modules/ecommerce/lib/cacheKeys.ts +340 -0
  531. package/src/modules/ecommerce/lib/createConflictRecheck.ts +31 -0
  532. package/src/modules/ecommerce/lib/crudEvents.ts +128 -0
  533. package/src/modules/ecommerce/lib/crudSupport.ts +85 -0
  534. package/src/modules/ecommerce/lib/domainBindingRebind.ts +138 -0
  535. package/src/modules/ecommerce/lib/domainMappingSummaries.ts +71 -0
  536. package/src/modules/ecommerce/lib/exclusiveFlag.ts +90 -0
  537. package/src/modules/ecommerce/lib/references.ts +93 -0
  538. package/src/modules/ecommerce/lib/seedDraftStore.ts +125 -0
  539. package/src/modules/ecommerce/lib/storeBindingCascade.ts +138 -0
  540. package/src/modules/ecommerce/lib/storeBranding.ts +48 -0
  541. package/src/modules/ecommerce/lib/storeBrandingRoute.ts +91 -0
  542. package/src/modules/ecommerce/lib/storeContext.ts +669 -0
  543. package/src/modules/ecommerce/lib/storeContextService.ts +71 -0
  544. package/src/modules/ecommerce/lib/storeLocale.ts +88 -0
  545. package/src/modules/ecommerce/lib/storeNotifications.ts +55 -0
  546. package/src/modules/ecommerce/lib/storefrontCatalogSupport.ts +427 -0
  547. package/src/modules/ecommerce/lib/storefrontCategories.ts +419 -0
  548. package/src/modules/ecommerce/lib/storefrontCategoryCache.ts +113 -0
  549. package/src/modules/ecommerce/lib/storefrontDetail.ts +654 -0
  550. package/src/modules/ecommerce/lib/storefrontFacets.ts +697 -0
  551. package/src/modules/ecommerce/lib/storefrontOptionFilter.ts +44 -0
  552. package/src/modules/ecommerce/lib/storefrontPricing.ts +583 -0
  553. package/src/modules/ecommerce/lib/storefrontProductCache.ts +126 -0
  554. package/src/modules/ecommerce/lib/storefrontProductScope.ts +181 -0
  555. package/src/modules/ecommerce/lib/storefrontProducts.ts +963 -0
  556. package/src/modules/ecommerce/lib/storefrontQuery.ts +215 -0
  557. package/src/modules/ecommerce/lib/storefrontRateLimit.ts +126 -0
  558. package/src/modules/ecommerce/lib/storefrontSearch.ts +454 -0
  559. package/src/modules/ecommerce/lib/storefrontSearchCache.ts +49 -0
  560. package/src/modules/ecommerce/lib/subscriberSupport.ts +41 -0
  561. package/src/modules/ecommerce/lib/types.ts +69 -0
  562. package/src/modules/ecommerce/migrations/.snapshot-open-mercato.json +829 -0
  563. package/src/modules/ecommerce/migrations/Migration20261005113233_ecommerce.ts +32 -0
  564. package/src/modules/ecommerce/migrations/Migration20261006112733_ecommerce.ts +15 -0
  565. package/src/modules/ecommerce/notifications.client.ts +46 -0
  566. package/src/modules/ecommerce/notifications.ts +46 -0
  567. package/src/modules/ecommerce/search.ts +93 -0
  568. package/src/modules/ecommerce/setup.ts +15 -0
  569. package/src/modules/ecommerce/subscribers/assortment-empty-notifications.ts +12 -0
  570. package/src/modules/ecommerce/subscribers/availability-policy-cache-invalidation.ts +12 -0
  571. package/src/modules/ecommerce/subscribers/catalog-category-cache-invalidation.ts +12 -0
  572. package/src/modules/ecommerce/subscribers/catalog-price-cache-invalidation.ts +12 -0
  573. package/src/modules/ecommerce/subscribers/catalog-product-cache-invalidation.ts +12 -0
  574. package/src/modules/ecommerce/subscribers/catalog-variant-cache-invalidation.ts +12 -0
  575. package/src/modules/ecommerce/subscribers/customer-group-cache-invalidation.ts +12 -0
  576. package/src/modules/ecommerce/subscribers/customer-group-membership-cache-invalidation.ts +12 -0
  577. package/src/modules/ecommerce/subscribers/customer-group-terms-cache-invalidation.ts +12 -0
  578. package/src/modules/ecommerce/subscribers/customer-user-deleted-cache-invalidation.ts +12 -0
  579. package/src/modules/ecommerce/subscribers/customer-user-updated-cache-invalidation.ts +12 -0
  580. package/src/modules/ecommerce/subscribers/domain-binding-rebind.ts +14 -0
  581. package/src/modules/ecommerce/subscribers/domain-mapping-cache-invalidation.ts +12 -0
  582. package/src/modules/ecommerce/subscribers/store-cache-invalidation.ts +12 -0
  583. package/src/modules/ecommerce/subscribers/store-channel-binding-cache-invalidation.ts +12 -0
  584. package/src/modules/ecommerce/subscribers/store-domain-binding-cache-invalidation.ts +12 -0
  585. package/src/modules/ecommerce/subscribers/store-notifications.ts +12 -0
  586. package/src/modules/query_index/lib/batch.ts +25 -9
  587. package/src/modules/query_index/lib/doc-enrichers.ts +306 -0
  588. package/src/modules/query_index/lib/document.ts +2 -0
  589. package/src/modules/query_index/lib/engine.ts +58 -1
  590. package/src/modules/query_index/lib/indexer.ts +8 -0
  591. package/src/modules/query_index/lib/search-tokens.ts +2 -0
  592. package/src/modules/query_index/migrations/Migration20261005201500_query_index.ts +42 -0
  593. package/src/modules/translations/components/TranslationManager.tsx +3 -3
  594. package/src/modules/translations/data/validators.ts +1 -1
  595. package/src/modules/translations/lib/batch.ts +47 -0
  596. package/src/modules/translations/lib/helpers.ts +4 -0
  597. package/src/modules/translations/lib/resolve-field-list.ts +16 -2
@@ -0,0 +1,669 @@
1
+ import type { EntityManager } from '@mikro-orm/postgresql'
2
+ import type { AssortmentScope } from '@open-mercato/shared/lib/catalog-visibility'
3
+ import { parseBooleanWithDefault } from '@open-mercato/shared/lib/boolean'
4
+ import { createLogger } from '@open-mercato/shared/lib/logger'
5
+ import { getTelemetryRuntime } from '@open-mercato/shared/lib/telemetry/runtime'
6
+ import { secretEqual } from '@open-mercato/core/modules/customer_accounts/lib/secretCompare'
7
+ import {
8
+ ECOMMERCE_STORE_SLUG_PATTERN,
9
+ ecommercePriceSortFallbackSchema,
10
+ ecommerceStoreSettingsSchema,
11
+ ecommerceStoreStatusSchema,
12
+ type EcommercePriceSortFallback,
13
+ type EcommerceStoreSettings,
14
+ type EcommerceStoreStatus,
15
+ } from '../data/validators'
16
+ import { emitEcommerceEvent } from '../events'
17
+ import {
18
+ ecommerceDomainMappingTag,
19
+ ecommerceDomainTag,
20
+ ecommerceResolutionCache,
21
+ ecommerceStoreTag,
22
+ type CacheContainer,
23
+ } from './cacheKeys'
24
+ import { resolveStoreLocale, type StoreLocaleCandidates } from './storeLocale'
25
+ import type { StoreContextChannel, StoreContextStore } from './types'
26
+
27
+ /**
28
+ * Store layer of storefront resolution (SPEC-029 §4.1 steps 1–5 and 7, §6.2, §8, §8.1).
29
+ *
30
+ * Host flow: normalized Host → `customer_accounts` `domainMappingService.resolveByHostname`
31
+ * (returns `active` mappings only, D1) → ONE ecommerce query returning every binding of that
32
+ * mapping joined with its store and the store's default channel binding (D6) → longest
33
+ * `path_prefix` match on a path-segment boundary (R6). The candidate list is cached per mapping.
34
+ *
35
+ * Dev slug flow (D18): `?storeSlug=` only when `OM_ECOMMERCE_DEV_STORE_SLUG` is true, otherwise
36
+ * `400`. A slug is unique within a tenant (§5.1), so slug resolution is tenant-scoped when the
37
+ * caller supplies a tenant and otherwise requires the slug to be unique across tenants — an
38
+ * ambiguous slug answers `404`.
39
+ *
40
+ * The buyer layer (Step 2.7) composes `ResolvedStore` into a full `StoreContext`.
41
+ */
42
+
43
+ export const DEV_STORE_SLUG_ENV = 'OM_ECOMMERCE_DEV_STORE_SLUG'
44
+ export const STORE_RESOLUTION_TTL_MS = 300_000
45
+ export const STORE_MISCONFIGURED_THROTTLE_MS = 3_600_000
46
+
47
+ export type StorefrontResolutionErrorCode =
48
+ | 'store_slug_not_allowed'
49
+ | 'store_not_found'
50
+ | 'store_draft'
51
+ | 'store_archived'
52
+ | 'store_misconfigured'
53
+ | 'portal_session_scope_mismatch'
54
+ | 'portal_session_invalid'
55
+
56
+ export type StorefrontResolutionStatus = 400 | 401 | 403 | 404 | 410 | 503
57
+
58
+ const STOREFRONT_RESOLUTION_ERROR_MARKER = Symbol.for('@open-mercato/ecommerce/StorefrontResolutionError')
59
+
60
+ export class StorefrontResolutionError extends Error {
61
+ readonly [STOREFRONT_RESOLUTION_ERROR_MARKER] = true
62
+ readonly status: StorefrontResolutionStatus
63
+ readonly code: StorefrontResolutionErrorCode
64
+
65
+ constructor(status: StorefrontResolutionStatus, code: StorefrontResolutionErrorCode) {
66
+ super(`[internal] storefront store resolution failed: ${code}`)
67
+ this.name = 'StorefrontResolutionError'
68
+ this.status = status
69
+ this.code = code
70
+ }
71
+ }
72
+
73
+ /**
74
+ * Marker-based so it holds across bundle layers: `storeContextService` is resolved from the DI
75
+ * container, whose registrars may have been loaded from another bundle layer than the route, so
76
+ * the error class it throws is not always the route's `StorefrontResolutionError`.
77
+ */
78
+ export function isStorefrontResolutionError(error: unknown): error is StorefrontResolutionError {
79
+ return (
80
+ !!error &&
81
+ typeof error === 'object' &&
82
+ (error as Record<symbol, unknown>)[STOREFRONT_RESOLUTION_ERROR_MARKER] === true
83
+ )
84
+ }
85
+
86
+ export type ResolvedStoreChannel = StoreContextChannel & {
87
+ assortmentScope: AssortmentScope | null
88
+ requireAuthentication: boolean
89
+ }
90
+
91
+ export type ResolvedStoreDomain = {
92
+ domainMappingId: string
93
+ hostname: string
94
+ bindingId: string
95
+ pathPrefix: string | null
96
+ isPrimary: boolean
97
+ }
98
+
99
+ export type ResolvedStore = {
100
+ source: 'host' | 'slug'
101
+ store: StoreContextStore
102
+ tenantId: string
103
+ organizationId: string
104
+ channel: ResolvedStoreChannel
105
+ domain: ResolvedStoreDomain | null
106
+ effectiveLocale: string
107
+ requestedLocale: string | null
108
+ currencyCode: string
109
+ }
110
+
111
+ export type StoreContextContainer = CacheContainer
112
+
113
+ export type ResolveStoreFromRequestOptions = {
114
+ pathname?: string | null
115
+ }
116
+
117
+ export type ResolveStoreBySlugOptions = {
118
+ tenantId?: string | null
119
+ locale?: string | null
120
+ headerLocale?: string | null
121
+ acceptLanguage?: string | null
122
+ }
123
+
124
+ type DomainMappingResolution = {
125
+ domainMappingId: string
126
+ hostname: string
127
+ tenantId: string
128
+ organizationId: string
129
+ status: string
130
+ }
131
+
132
+ type DomainMappingResolver = {
133
+ resolveByHostname(hostname: string): Promise<DomainMappingResolution | null>
134
+ }
135
+
136
+ type CandidateStore = Omit<StoreContextStore, 'status'> & {
137
+ status: EcommerceStoreStatus
138
+ tenantId: string
139
+ organizationId: string
140
+ }
141
+
142
+ type CandidateBinding = { id: string; pathPrefix: string | null; isPrimary: boolean }
143
+
144
+ type StoreCandidate = {
145
+ store: CandidateStore
146
+ channel: ResolvedStoreChannel | null
147
+ binding: CandidateBinding | null
148
+ }
149
+
150
+ type CachedStoreCandidates = { candidates: StoreCandidate[] }
151
+
152
+ type StoreResolutionDatabase = {
153
+ ecommerce_stores: {
154
+ id: string
155
+ tenant_id: string
156
+ organization_id: string
157
+ code: string
158
+ name: string
159
+ slug: string
160
+ status: string
161
+ default_locale: string
162
+ supported_locales: unknown
163
+ default_currency_code: string
164
+ settings: unknown
165
+ deleted_at: Date | null
166
+ }
167
+ ecommerce_store_domain_bindings: {
168
+ id: string
169
+ tenant_id: string
170
+ organization_id: string
171
+ store_id: string
172
+ domain_mapping_id: string
173
+ path_prefix: string | null
174
+ is_primary: boolean
175
+ deleted_at: Date | null
176
+ }
177
+ ecommerce_store_channel_bindings: {
178
+ id: string
179
+ tenant_id: string
180
+ organization_id: string
181
+ store_id: string
182
+ sales_channel_id: string
183
+ price_kind_id: string | null
184
+ assortment_scope: unknown
185
+ price_sort_fallback: string
186
+ is_default: boolean
187
+ require_authentication: boolean
188
+ deleted_at: Date | null
189
+ }
190
+ }
191
+
192
+ type StoreResolutionRow = {
193
+ store_id: string
194
+ store_tenant_id: string
195
+ store_organization_id: string
196
+ store_code: string
197
+ store_name: string
198
+ store_slug: string
199
+ store_status: string
200
+ store_default_locale: string
201
+ store_supported_locales: unknown
202
+ store_default_currency_code: string
203
+ store_settings: unknown
204
+ channel_binding_id: string | null
205
+ channel_sales_channel_id: string | null
206
+ channel_price_kind_id: string | null
207
+ channel_assortment_scope: unknown
208
+ channel_price_sort_fallback: string | null
209
+ channel_require_authentication?: boolean | null
210
+ binding_id?: string | null
211
+ binding_path_prefix?: string | null
212
+ binding_is_primary?: boolean | null
213
+ }
214
+
215
+ const STORE_COLUMNS = [
216
+ 's.id as store_id',
217
+ 's.tenant_id as store_tenant_id',
218
+ 's.organization_id as store_organization_id',
219
+ 's.code as store_code',
220
+ 's.name as store_name',
221
+ 's.slug as store_slug',
222
+ 's.status as store_status',
223
+ 's.default_locale as store_default_locale',
224
+ 's.supported_locales as store_supported_locales',
225
+ 's.default_currency_code as store_default_currency_code',
226
+ 's.settings as store_settings',
227
+ 'c.id as channel_binding_id',
228
+ 'c.sales_channel_id as channel_sales_channel_id',
229
+ 'c.price_kind_id as channel_price_kind_id',
230
+ 'c.assortment_scope as channel_assortment_scope',
231
+ 'c.price_sort_fallback as channel_price_sort_fallback',
232
+ 'c.require_authentication as channel_require_authentication',
233
+ ] as const
234
+
235
+ const FORCE_HOST_HEADER = 'x-force-host'
236
+ const FORCE_HOST_SECRET_HEADER = 'x-force-host-secret'
237
+ const SLUG_MAX_LENGTH = 120
238
+
239
+ const logger = createLogger('ecommerce').child({ component: 'store-context' })
240
+
241
+ type NotFoundReason =
242
+ | 'host_missing'
243
+ | 'domain_routing_unavailable'
244
+ | 'domain_mapping_not_active'
245
+ | 'domain_binding_not_found'
246
+ | 'store_draft_hidden'
247
+ | 'slug_invalid'
248
+ | 'slug_not_found'
249
+ | 'slug_ambiguous'
250
+
251
+ const WARN_NOT_FOUND_REASONS: ReadonlySet<NotFoundReason> = new Set([
252
+ 'domain_routing_unavailable',
253
+ 'domain_binding_not_found',
254
+ 'slug_ambiguous',
255
+ ])
256
+
257
+ function storeNotFound(reason: NotFoundReason, details: Record<string, unknown>): StorefrontResolutionError {
258
+ const meta = { reason, ...details }
259
+ if (WARN_NOT_FOUND_REASONS.has(reason)) {
260
+ logger.warn('Storefront store not resolved', meta)
261
+ } else {
262
+ logger.debug('Storefront store not resolved', meta)
263
+ }
264
+ return new StorefrontResolutionError(404, 'store_not_found')
265
+ }
266
+
267
+ function tryResolve<T>(container: StoreContextContainer, name: string): T | null {
268
+ try {
269
+ const resolved = container.resolve(name) as T | null | undefined
270
+ return resolved ?? null
271
+ } catch {
272
+ return null
273
+ }
274
+ }
275
+
276
+ function resolveEntityManager(container: StoreContextContainer): EntityManager {
277
+ const em = tryResolve<EntityManager>(container, 'em')
278
+ if (!em) throw new Error('[internal] ecommerce store resolution requires the DI entity manager')
279
+ return em
280
+ }
281
+
282
+ export function isDevStoreSlugEnabled(): boolean {
283
+ return parseBooleanWithDefault(process.env[DEV_STORE_SLUG_ENV], false)
284
+ }
285
+
286
+ export function normalizeRequestHost(raw: string | null | undefined): string | null {
287
+ if (typeof raw !== 'string') return null
288
+ const first = raw.split(',')[0]?.trim().toLowerCase() ?? ''
289
+ if (!first.length) return null
290
+ const withoutPort = first.startsWith('[')
291
+ ? first.replace(/^(\[[^\]]*\])(?::\d+)?$/, '$1')
292
+ : first.replace(/:\d+$/, '')
293
+ const withoutTrailingDot = withoutPort.replace(/\.+$/, '')
294
+ return withoutTrailingDot.length ? withoutTrailingDot : null
295
+ }
296
+
297
+ export function normalizeRequestPathname(pathname: string | null | undefined): string {
298
+ if (typeof pathname !== 'string' || !pathname.length) return '/'
299
+ const lowered = pathname.toLowerCase()
300
+ const withLeadingSlash = lowered.startsWith('/') ? lowered : `/${lowered}`
301
+ const withoutTrailingSlash = withLeadingSlash.replace(/\/+$/, '')
302
+ return withoutTrailingSlash.length ? withoutTrailingSlash : '/'
303
+ }
304
+
305
+ export function pathPrefixMatches(pathPrefix: string | null, pathname: string): boolean {
306
+ if (!pathPrefix) return true
307
+ const normalizedPrefix = normalizeRequestPathname(pathPrefix)
308
+ if (normalizedPrefix === '/') return true
309
+ return pathname === normalizedPrefix || pathname.startsWith(`${normalizedPrefix}/`)
310
+ }
311
+
312
+ export function selectLongestPrefixMatch<T extends { binding: { pathPrefix: string | null } | null }>(
313
+ candidates: T[],
314
+ pathname: string,
315
+ ): T | null {
316
+ let best: T | null = null
317
+ let bestLength = -1
318
+ for (const candidate of candidates) {
319
+ const pathPrefix = candidate.binding?.pathPrefix ?? null
320
+ if (!pathPrefixMatches(pathPrefix, pathname)) continue
321
+ const length = pathPrefix ? normalizeRequestPathname(pathPrefix).length : 0
322
+ if (length > bestLength) {
323
+ best = candidate
324
+ bestLength = length
325
+ }
326
+ }
327
+ return best
328
+ }
329
+
330
+ function readForcedHost(request: Request): string | null {
331
+ if (process.env.NODE_ENV !== 'test') return null
332
+ const expected = process.env.FORCE_HOST_SECRET
333
+ if (!expected) return null
334
+ if (!secretEqual(request.headers.get(FORCE_HOST_SECRET_HEADER), expected)) return null
335
+ const forced = request.headers.get(FORCE_HOST_HEADER)
336
+ return forced && forced.length > 0 ? forced : null
337
+ }
338
+
339
+ function readLocaleCandidates(request: Request, url: URL): StoreLocaleCandidates {
340
+ return {
341
+ queryLocale: url.searchParams.get('locale'),
342
+ headerLocale: request.headers.get('x-locale'),
343
+ acceptLanguage: request.headers.get('accept-language'),
344
+ }
345
+ }
346
+
347
+ function parseJsonColumn(value: unknown): unknown {
348
+ if (typeof value !== 'string') return value
349
+ try {
350
+ return JSON.parse(value)
351
+ } catch {
352
+ return null
353
+ }
354
+ }
355
+
356
+ function isRecord(value: unknown): value is Record<string, unknown> {
357
+ return typeof value === 'object' && value !== null && !Array.isArray(value)
358
+ }
359
+
360
+ function parseSettings(raw: unknown, storeId: string): EcommerceStoreSettings {
361
+ const parsed = ecommerceStoreSettingsSchema.safeParse(parseJsonColumn(raw) ?? {})
362
+ if (parsed.success) return parsed.data
363
+ logger.warn('Store settings failed validation; serving defaults', { storeId })
364
+ return ecommerceStoreSettingsSchema.parse({})
365
+ }
366
+
367
+ function parseSupportedLocales(raw: unknown, defaultLocale: string): string[] {
368
+ const value = parseJsonColumn(raw)
369
+ const locales = Array.isArray(value)
370
+ ? value.filter((locale): locale is string => typeof locale === 'string' && locale.length > 0)
371
+ : []
372
+ return locales.includes(defaultLocale) ? locales : [defaultLocale, ...locales]
373
+ }
374
+
375
+ function parseStatus(raw: string): EcommerceStoreStatus {
376
+ const parsed = ecommerceStoreStatusSchema.safeParse(raw)
377
+ return parsed.success ? parsed.data : 'draft'
378
+ }
379
+
380
+ function parsePriceSortFallback(raw: string | null): EcommercePriceSortFallback {
381
+ const parsed = ecommercePriceSortFallbackSchema.safeParse(raw)
382
+ return parsed.success ? parsed.data : 'approximate'
383
+ }
384
+
385
+ function parseAssortmentScope(raw: unknown): AssortmentScope | null {
386
+ const value = parseJsonColumn(raw)
387
+ return isRecord(value) ? (value as AssortmentScope) : null
388
+ }
389
+
390
+ function toCandidate(row: StoreResolutionRow): StoreCandidate {
391
+ const store: CandidateStore = {
392
+ id: row.store_id,
393
+ tenantId: row.store_tenant_id,
394
+ organizationId: row.store_organization_id,
395
+ code: row.store_code,
396
+ name: row.store_name,
397
+ slug: row.store_slug,
398
+ status: parseStatus(row.store_status),
399
+ defaultLocale: row.store_default_locale,
400
+ supportedLocales: parseSupportedLocales(row.store_supported_locales, row.store_default_locale),
401
+ defaultCurrencyCode: row.store_default_currency_code,
402
+ settings: parseSettings(row.store_settings, row.store_id),
403
+ }
404
+ const channel: ResolvedStoreChannel | null =
405
+ row.channel_binding_id && row.channel_sales_channel_id
406
+ ? {
407
+ channelBindingId: row.channel_binding_id,
408
+ salesChannelId: row.channel_sales_channel_id,
409
+ priceKindId: row.channel_price_kind_id ?? null,
410
+ priceSortFallback: parsePriceSortFallback(row.channel_price_sort_fallback),
411
+ assortmentScope: parseAssortmentScope(row.channel_assortment_scope),
412
+ requireAuthentication: row.channel_require_authentication === true,
413
+ }
414
+ : null
415
+ const binding: CandidateBinding | null = row.binding_id
416
+ ? { id: row.binding_id, pathPrefix: row.binding_path_prefix ?? null, isPrimary: row.binding_is_primary === true }
417
+ : null
418
+ return { store, channel, binding }
419
+ }
420
+
421
+ async function queryDomainCandidates(
422
+ em: EntityManager,
423
+ mapping: DomainMappingResolution,
424
+ ): Promise<StoreResolutionRow[]> {
425
+ const db = em.getKysely<StoreResolutionDatabase>()
426
+ return db
427
+ .selectFrom('ecommerce_store_domain_bindings as b')
428
+ .innerJoin('ecommerce_stores as s', (join) =>
429
+ join
430
+ .onRef('s.id', '=', 'b.store_id')
431
+ .onRef('s.tenant_id', '=', 'b.tenant_id')
432
+ .onRef('s.organization_id', '=', 'b.organization_id')
433
+ .on('s.deleted_at', 'is', null),
434
+ )
435
+ .leftJoin('ecommerce_store_channel_bindings as c', (join) =>
436
+ join
437
+ .onRef('c.store_id', '=', 's.id')
438
+ .onRef('c.tenant_id', '=', 's.tenant_id')
439
+ .onRef('c.organization_id', '=', 's.organization_id')
440
+ .on('c.is_default', '=', true)
441
+ .on('c.deleted_at', 'is', null),
442
+ )
443
+ .select([
444
+ ...STORE_COLUMNS,
445
+ 'b.id as binding_id',
446
+ 'b.path_prefix as binding_path_prefix',
447
+ 'b.is_primary as binding_is_primary',
448
+ ])
449
+ .where('b.domain_mapping_id', '=', mapping.domainMappingId)
450
+ .where('b.tenant_id', '=', mapping.tenantId)
451
+ .where('b.organization_id', '=', mapping.organizationId)
452
+ .where('b.deleted_at', 'is', null)
453
+ .execute()
454
+ }
455
+
456
+ async function querySlugCandidates(
457
+ em: EntityManager,
458
+ slug: string,
459
+ tenantId: string | null,
460
+ ): Promise<StoreResolutionRow[]> {
461
+ const db = em.getKysely<StoreResolutionDatabase>()
462
+ let query = db
463
+ .selectFrom('ecommerce_stores as s')
464
+ .leftJoin('ecommerce_store_channel_bindings as c', (join) =>
465
+ join
466
+ .onRef('c.store_id', '=', 's.id')
467
+ .onRef('c.tenant_id', '=', 's.tenant_id')
468
+ .onRef('c.organization_id', '=', 's.organization_id')
469
+ .on('c.is_default', '=', true)
470
+ .on('c.deleted_at', 'is', null),
471
+ )
472
+ .select([...STORE_COLUMNS])
473
+ .where('s.slug', '=', slug)
474
+ .where('s.deleted_at', 'is', null)
475
+ if (tenantId) query = query.where('s.tenant_id', '=', tenantId)
476
+ return query.limit(2).execute()
477
+ }
478
+
479
+ async function loadDomainCandidates(
480
+ container: StoreContextContainer,
481
+ mapping: DomainMappingResolution,
482
+ ): Promise<StoreCandidate[]> {
483
+ const cache = ecommerceResolutionCache(container)
484
+ const key = ['domain-mapping', mapping.domainMappingId]
485
+ const cached = await cache.get<CachedStoreCandidates>(key)
486
+ if (cached && Array.isArray(cached.candidates)) return cached.candidates
487
+ const rows = await queryDomainCandidates(resolveEntityManager(container), mapping)
488
+ const candidates = rows.map(toCandidate)
489
+ const tags = [
490
+ ecommerceDomainMappingTag(mapping.domainMappingId),
491
+ ecommerceDomainTag(mapping.hostname),
492
+ ...candidates.map((candidate) => ecommerceStoreTag(candidate.store.id)),
493
+ ]
494
+ await cache.set<CachedStoreCandidates>(
495
+ key,
496
+ { candidates },
497
+ { ttlMs: STORE_RESOLUTION_TTL_MS, tags: Array.from(new Set(tags)) },
498
+ )
499
+ return candidates
500
+ }
501
+
502
+ async function loadSlugCandidates(
503
+ container: StoreContextContainer,
504
+ slug: string,
505
+ tenantId: string | null,
506
+ ): Promise<StoreCandidate[]> {
507
+ const cache = ecommerceResolutionCache(container)
508
+ const key = ['slug', tenantId ?? '*', slug]
509
+ const cached = await cache.get<CachedStoreCandidates>(key)
510
+ if (cached && Array.isArray(cached.candidates)) return cached.candidates
511
+ const rows = await querySlugCandidates(resolveEntityManager(container), slug, tenantId)
512
+ const candidates = rows.map(toCandidate)
513
+ if (candidates.length > 0) {
514
+ await cache.set<CachedStoreCandidates>(
515
+ key,
516
+ { candidates },
517
+ {
518
+ ttlMs: STORE_RESOLUTION_TTL_MS,
519
+ tags: candidates.map((candidate) => ecommerceStoreTag(candidate.store.id)),
520
+ },
521
+ )
522
+ }
523
+ return candidates
524
+ }
525
+
526
+ async function reportMisconfiguredStore(container: StoreContextContainer, store: CandidateStore): Promise<void> {
527
+ const cache = ecommerceResolutionCache(container)
528
+ const key = ['misconfigured', 'channel-binding-missing', store.id]
529
+ const alreadyReported = await cache.get<{ reportedAt: string }>(key)
530
+ if (alreadyReported) return
531
+ await cache.set(key, { reportedAt: new Date().toISOString() }, { ttlMs: STORE_MISCONFIGURED_THROTTLE_MS })
532
+ logger.warn('Store has no default channel binding; answering 503', {
533
+ storeId: store.id,
534
+ tenantId: store.tenantId,
535
+ organizationId: store.organizationId,
536
+ })
537
+ try {
538
+ await emitEcommerceEvent(
539
+ 'ecommerce.store.misconfigured',
540
+ {
541
+ id: store.id,
542
+ storeId: store.id,
543
+ tenantId: store.tenantId,
544
+ organizationId: store.organizationId,
545
+ reason: 'channel_binding_missing',
546
+ },
547
+ { persistent: true, tenantId: store.tenantId, organizationId: store.organizationId },
548
+ )
549
+ } catch (error) {
550
+ logger.warn('Failed to emit store misconfiguration event', { storeId: store.id, err: error })
551
+ getTelemetryRuntime()?.reportError(error, {
552
+ module: 'ecommerce',
553
+ code: 'ecommerce.store_misconfigured_emit_failed',
554
+ attributes: { storeId: store.id },
555
+ })
556
+ }
557
+ }
558
+
559
+ async function finalizeCandidate(
560
+ container: StoreContextContainer,
561
+ candidate: StoreCandidate,
562
+ source: ResolvedStore['source'],
563
+ domain: ResolvedStoreDomain | null,
564
+ localeCandidates: StoreLocaleCandidates,
565
+ ): Promise<ResolvedStore> {
566
+ const { store } = candidate
567
+ if (store.status === 'draft') {
568
+ if (isDevStoreSlugEnabled()) throw new StorefrontResolutionError(403, 'store_draft')
569
+ throw storeNotFound('store_draft_hidden', { source, storeId: store.id })
570
+ }
571
+ if (store.status === 'archived') throw new StorefrontResolutionError(410, 'store_archived')
572
+ if (!candidate.channel) {
573
+ await reportMisconfiguredStore(container, store)
574
+ throw new StorefrontResolutionError(503, 'store_misconfigured')
575
+ }
576
+ const locale = resolveStoreLocale(store, localeCandidates)
577
+ return {
578
+ source,
579
+ store: {
580
+ id: store.id,
581
+ code: store.code,
582
+ name: store.name,
583
+ slug: store.slug,
584
+ status: 'active',
585
+ defaultLocale: store.defaultLocale,
586
+ supportedLocales: store.supportedLocales,
587
+ defaultCurrencyCode: store.defaultCurrencyCode,
588
+ settings: store.settings,
589
+ },
590
+ tenantId: store.tenantId,
591
+ organizationId: store.organizationId,
592
+ channel: candidate.channel,
593
+ domain,
594
+ effectiveLocale: locale.effectiveLocale,
595
+ requestedLocale: locale.requestedLocale,
596
+ currencyCode: store.defaultCurrencyCode,
597
+ }
598
+ }
599
+
600
+ async function resolveSlugWithCandidates(
601
+ container: StoreContextContainer,
602
+ rawSlug: string,
603
+ tenantId: string | null,
604
+ localeCandidates: StoreLocaleCandidates,
605
+ ): Promise<ResolvedStore> {
606
+ if (!isDevStoreSlugEnabled()) throw new StorefrontResolutionError(400, 'store_slug_not_allowed')
607
+ const slug = rawSlug.trim().toLowerCase()
608
+ if (!slug.length || slug.length > SLUG_MAX_LENGTH || !ECOMMERCE_STORE_SLUG_PATTERN.test(slug)) {
609
+ throw storeNotFound('slug_invalid', {})
610
+ }
611
+ const candidates = await loadSlugCandidates(container, slug, tenantId)
612
+ if (candidates.length === 0) throw storeNotFound('slug_not_found', { slug })
613
+ if (candidates.length > 1) throw storeNotFound('slug_ambiguous', { slug })
614
+ return finalizeCandidate(container, candidates[0], 'slug', null, localeCandidates)
615
+ }
616
+
617
+ export async function resolveStoreBySlug(
618
+ container: StoreContextContainer,
619
+ slug: string,
620
+ options: ResolveStoreBySlugOptions = {},
621
+ ): Promise<ResolvedStore> {
622
+ return resolveSlugWithCandidates(container, slug, options.tenantId ?? null, {
623
+ queryLocale: options.locale ?? null,
624
+ headerLocale: options.headerLocale ?? null,
625
+ acceptLanguage: options.acceptLanguage ?? null,
626
+ })
627
+ }
628
+
629
+ export async function resolveStoreFromRequest(
630
+ container: StoreContextContainer,
631
+ request: Request,
632
+ options: ResolveStoreFromRequestOptions = {},
633
+ ): Promise<ResolvedStore> {
634
+ const url = new URL(request.url)
635
+ const localeCandidates = readLocaleCandidates(request, url)
636
+ const storeSlug = url.searchParams.get('storeSlug')
637
+ if (storeSlug !== null) return resolveSlugWithCandidates(container, storeSlug, null, localeCandidates)
638
+
639
+ const host = normalizeRequestHost(readForcedHost(request) ?? request.headers.get('host'))
640
+ if (!host) throw storeNotFound('host_missing', {})
641
+ const domainMappingService = tryResolve<DomainMappingResolver>(container, 'domainMappingService')
642
+ if (!domainMappingService) throw storeNotFound('domain_routing_unavailable', { host })
643
+ const mapping = await domainMappingService.resolveByHostname(host)
644
+ if (!mapping || mapping.status !== 'active') throw storeNotFound('domain_mapping_not_active', { host })
645
+
646
+ const candidates = await loadDomainCandidates(container, mapping)
647
+ const pathname = normalizeRequestPathname(options.pathname ?? url.pathname)
648
+ const candidate = selectLongestPrefixMatch(candidates, pathname)
649
+ if (!candidate?.binding) {
650
+ throw storeNotFound('domain_binding_not_found', {
651
+ host,
652
+ domainMappingId: mapping.domainMappingId,
653
+ bindingCount: candidates.length,
654
+ })
655
+ }
656
+ return finalizeCandidate(
657
+ container,
658
+ candidate,
659
+ 'host',
660
+ {
661
+ domainMappingId: mapping.domainMappingId,
662
+ hostname: mapping.hostname,
663
+ bindingId: candidate.binding.id,
664
+ pathPrefix: candidate.binding.pathPrefix,
665
+ isPrimary: candidate.binding.isPrimary,
666
+ },
667
+ localeCandidates,
668
+ )
669
+ }