@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,419 @@
1
+ import type { AwilixContainer } from 'awilix'
2
+ import type { EntityManager } from '@mikro-orm/postgresql'
3
+ import { sql } from 'kysely'
4
+ import { findWithDecryption } from '@open-mercato/shared/lib/encryption/find'
5
+ import type { QueryEngine } from '@open-mercato/shared/lib/query/types'
6
+ import { CatalogProductCategory } from '@open-mercato/core/modules/catalog/data/entities'
7
+ import { compareCodeUnits } from '@open-mercato/core/modules/catalog/lib/productScopeKeys'
8
+ import type { EcommerceStorefrontCategoryTreeQuery } from '../data/validators'
9
+ import {
10
+ STOREFRONT_CATEGORY_ENTITY_TYPE,
11
+ isCategoryInAssortment,
12
+ loadStorefrontTranslations,
13
+ localeChain,
14
+ localize,
15
+ stringList,
16
+ tryResolve,
17
+ type TranslationMap,
18
+ } from './storefrontCatalogSupport'
19
+ import { queryStorefrontProductUniverse, type StorefrontFacetRuntime } from './storefrontFacets'
20
+ import { buildStorefrontProductScope } from './storefrontProductScope'
21
+ import type { StoreContext } from './types'
22
+
23
+ /**
24
+ * Category tree and category landing (Storefront Public API rev 4 §4.3, §4.4).
25
+ *
26
+ * A category is visible to a buyer when it is active, every ancestor is active (so the tree can
27
+ * reach it) and `isCategoryInAssortment` admits it. Counts are descendant-inclusive and taken over
28
+ * the buyer's assortment universe (`buildStorefrontProductScope`: tenant, organization, active,
29
+ * not deleted, inside the effective scope), so a restricted buyer never sees a count for products
30
+ * they cannot browse. Nothing here depends on prices, so the results are keyed on
31
+ * `assortmentScopeHash` by `storefrontCategoryCache.ts`.
32
+ *
33
+ * Query shape, uncached: categories, the scoped product ids, the category assignments of those
34
+ * products, and one multi-entity translation query — four, regardless of tree size. A buyer whose
35
+ * assortment is deny-all (`[]`, e.g. a closed `require_authentication` channel) costs none.
36
+ *
37
+ * The product universe is capped at `STOREFRONT_FACET_UNIVERSE_CAP`: an assortment larger than
38
+ * that is not counted, every `productCount` is `null` and no category is dropped as empty.
39
+ */
40
+
41
+ export type StorefrontCategoryNode = {
42
+ id: string
43
+ name: string
44
+ slug: string | null
45
+ description: string | null
46
+ depth: number
47
+ parentId: string | null
48
+ productCount: number | null
49
+ hasChildren: boolean
50
+ children: StorefrontCategoryNode[]
51
+ }
52
+
53
+ export type StorefrontCategoryTreeResponse = {
54
+ tree: StorefrontCategoryNode[]
55
+ effectiveLocale: string
56
+ }
57
+
58
+ export type StorefrontCategoryBreadcrumbEntry = { id: string; name: string; slug: string | null }
59
+
60
+ export type StorefrontCategoryChild = { id: string; name: string; slug: string | null; productCount: number | null }
61
+
62
+ export type StorefrontCategorySeo = { title: string | null; description: string | null; canonicalUrl: string | null }
63
+
64
+ export type StorefrontCategoryLanding = {
65
+ id: string
66
+ name: string
67
+ slug: string | null
68
+ description: string | null
69
+ depth: number
70
+ parentId: string | null
71
+ ancestorIds: string[]
72
+ breadcrumb: StorefrontCategoryBreadcrumbEntry[]
73
+ children: StorefrontCategoryChild[]
74
+ productCount: number | null
75
+ seo: StorefrontCategorySeo
76
+ }
77
+
78
+ export type StorefrontCategoryLandingBlock = {
79
+ category: StorefrontCategoryLanding
80
+ effectiveLocale: string
81
+ }
82
+
83
+ type CategoryRow = {
84
+ id: string
85
+ name: string
86
+ slug: string | null
87
+ description: string | null
88
+ depth: number
89
+ parentId: string | null
90
+ isActive: boolean
91
+ ancestorIds: string[]
92
+ descendantIds: string[]
93
+ }
94
+
95
+ type CategorySnapshot = {
96
+ visible: Map<string, CategoryRow>
97
+ /** `null` when the assortment exceeds the universe cap and was not counted. */
98
+ counts: Map<string, number> | null
99
+ }
100
+
101
+ type CategoryAssignmentDatabase = {
102
+ catalog_product_category_assignments: {
103
+ product_id: string
104
+ category_id: string
105
+ tenant_id: string
106
+ organization_id: string
107
+ }
108
+ }
109
+
110
+ const EMPTY_SNAPSHOT: CategorySnapshot = { visible: new Map(), counts: new Map() }
111
+
112
+ function resolveRuntime(container: AwilixContainer, ctx: StoreContext): StorefrontFacetRuntime {
113
+ const em = tryResolve<EntityManager>(container, 'em')
114
+ const queryEngine = tryResolve<QueryEngine>(container, 'queryEngine')
115
+ if (!em || !queryEngine) throw new Error('[internal] ecommerce storefront categories require em and queryEngine')
116
+ return {
117
+ ctx,
118
+ em,
119
+ queryEngine,
120
+ scope: buildStorefrontProductScope(ctx),
121
+ decryptionScope: { tenantId: ctx.tenantId, organizationId: ctx.organizationId },
122
+ }
123
+ }
124
+
125
+ function isDenyAll(ctx: StoreContext): boolean {
126
+ const scope = ctx.buyer.assortmentScope
127
+ return scope !== null && scope.length === 0
128
+ }
129
+
130
+ type CategoryLoadRuntime = Pick<StorefrontFacetRuntime, 'ctx' | 'em' | 'decryptionScope'>
131
+
132
+ export type StorefrontVisibleCategory = CategoryRow
133
+
134
+ async function loadCategoryRows(runtime: CategoryLoadRuntime): Promise<Map<string, CategoryRow>> {
135
+ const { ctx } = runtime
136
+ const rows = await findWithDecryption(
137
+ runtime.em,
138
+ CatalogProductCategory,
139
+ { tenantId: ctx.tenantId, organizationId: ctx.organizationId, deletedAt: null },
140
+ { fields: ['id', 'name', 'slug', 'description', 'depth', 'parentId', 'isActive', 'ancestorIds', 'descendantIds'] },
141
+ runtime.decryptionScope,
142
+ )
143
+ return new Map(
144
+ rows.map((row) => [
145
+ row.id,
146
+ {
147
+ id: row.id,
148
+ name: row.name,
149
+ slug: row.slug ?? null,
150
+ description: row.description ?? null,
151
+ depth: typeof row.depth === 'number' ? row.depth : 0,
152
+ parentId: row.parentId ?? null,
153
+ isActive: row.isActive !== false,
154
+ ancestorIds: stringList(row.ancestorIds),
155
+ descendantIds: stringList(row.descendantIds),
156
+ },
157
+ ]),
158
+ )
159
+ }
160
+
161
+ function selectVisibleCategories(rows: Map<string, CategoryRow>, ctx: StoreContext): Map<string, CategoryRow> {
162
+ const visible = new Map<string, CategoryRow>()
163
+ for (const row of rows.values()) {
164
+ if (!row.isActive) continue
165
+ if (!row.ancestorIds.every((id) => rows.get(id)?.isActive === true)) continue
166
+ const lineage = { id: row.id, ancestorIds: row.ancestorIds, descendantIds: row.descendantIds }
167
+ if (!isCategoryInAssortment(lineage, ctx.buyer.assortmentScope)) continue
168
+ visible.set(row.id, row)
169
+ }
170
+ return visible
171
+ }
172
+
173
+ /** The tenant's categories visible to the buyer (see the module header): one categories query. */
174
+ export async function loadStorefrontVisibleCategories(runtime: CategoryLoadRuntime): Promise<Map<string, StorefrontVisibleCategory>> {
175
+ return selectVisibleCategories(await loadCategoryRows(runtime), runtime.ctx)
176
+ }
177
+
178
+ async function loadAssignedCategoryIds(
179
+ runtime: StorefrontFacetRuntime,
180
+ productIds: string[],
181
+ ): Promise<Map<string, string[]>> {
182
+ const { tenantId, organizationId } = runtime.decryptionScope
183
+ const rows = await runtime.em
184
+ .getKysely<CategoryAssignmentDatabase>()
185
+ .selectFrom('catalog_product_category_assignments as ca')
186
+ .select(['ca.product_id as product_id', 'ca.category_id as category_id'])
187
+ .where(sql<boolean>`${sql.ref('ca.product_id')} = any(${productIds}::uuid[])`)
188
+ .where('ca.tenant_id', '=', tenantId)
189
+ .where('ca.organization_id', '=', organizationId)
190
+ .execute()
191
+ const byProduct = new Map<string, string[]>()
192
+ for (const row of rows) {
193
+ const bucket = byProduct.get(row.product_id) ?? []
194
+ bucket.push(row.category_id)
195
+ byProduct.set(row.product_id, bucket)
196
+ }
197
+ return byProduct
198
+ }
199
+
200
+ /**
201
+ * Descendant-inclusive product counts: a product counts once for every category it is assigned to
202
+ * and once for each ancestor of those, however many of its assignments share an ancestor.
203
+ */
204
+ export function countProductsPerCategory(
205
+ assignedByProduct: Map<string, string[]>,
206
+ ancestorsOf: (categoryId: string) => string[] | null,
207
+ ): Map<string, number> {
208
+ const counts = new Map<string, number>()
209
+ for (const assigned of assignedByProduct.values()) {
210
+ const reached = new Set<string>()
211
+ for (const categoryId of assigned) {
212
+ const ancestors = ancestorsOf(categoryId)
213
+ if (!ancestors) continue
214
+ reached.add(categoryId)
215
+ for (const ancestorId of ancestors) reached.add(ancestorId)
216
+ }
217
+ for (const categoryId of reached) counts.set(categoryId, (counts.get(categoryId) ?? 0) + 1)
218
+ }
219
+ return counts
220
+ }
221
+
222
+ async function loadCategoryCounts(
223
+ runtime: StorefrontFacetRuntime,
224
+ rows: Map<string, CategoryRow>,
225
+ universe: Array<{ id?: unknown }> | null,
226
+ ): Promise<Map<string, number> | null> {
227
+ if (universe === null) return null
228
+ const productIds = Array.from(
229
+ new Set(universe.flatMap((record) => (typeof record.id === 'string' ? [record.id] : []))),
230
+ )
231
+ if (productIds.length === 0) return new Map()
232
+ const assigned = await loadAssignedCategoryIds(runtime, productIds)
233
+ return countProductsPerCategory(assigned, (categoryId) => rows.get(categoryId)?.ancestorIds ?? null)
234
+ }
235
+
236
+ /**
237
+ * With `targetVisible`, the categories query runs first and the product universe is loaded only
238
+ * when the requested category is visible, so an unknown slug or `parentId` costs one categories
239
+ * query instead of three catalogue-sized ones.
240
+ */
241
+ async function loadCategorySnapshot(
242
+ runtime: StorefrontFacetRuntime,
243
+ targetVisible?: (visible: Map<string, CategoryRow>) => boolean,
244
+ ): Promise<CategorySnapshot> {
245
+ if (isDenyAll(runtime.ctx)) return EMPTY_SNAPSHOT
246
+ if (targetVisible) {
247
+ const rows = await loadCategoryRows(runtime)
248
+ const visible = selectVisibleCategories(rows, runtime.ctx)
249
+ if (visible.size === 0 || !targetVisible(visible)) return { visible, counts: new Map() }
250
+ const universe = await queryStorefrontProductUniverse(runtime, runtime.scope.filters)
251
+ return { visible, counts: await loadCategoryCounts(runtime, rows, universe) }
252
+ }
253
+ const [rows, universe] = await Promise.all([
254
+ loadCategoryRows(runtime),
255
+ queryStorefrontProductUniverse(runtime, runtime.scope.filters),
256
+ ])
257
+ const visible = selectVisibleCategories(rows, runtime.ctx)
258
+ if (visible.size === 0) return { visible, counts: new Map() }
259
+ return { visible, counts: await loadCategoryCounts(runtime, rows, universe) }
260
+ }
261
+
262
+ function productCountOf(counts: Map<string, number> | null, id: string): number | null {
263
+ return counts === null ? null : counts.get(id) ?? 0
264
+ }
265
+
266
+ function isNonEmpty(counts: Map<string, number> | null, id: string): boolean {
267
+ return counts === null || (counts.get(id) ?? 0) > 0
268
+ }
269
+
270
+ function compareNodes(left: { name: string; id: string }, right: { name: string; id: string }): number {
271
+ return left.name.localeCompare(right.name) || compareCodeUnits(left.id, right.id)
272
+ }
273
+
274
+ function localizedName(row: CategoryRow, translations: TranslationMap | undefined, locales: string[]): string {
275
+ return localize(row.name, translations?.get(row.id), 'name', locales) ?? row.name
276
+ }
277
+
278
+ function localizedDescription(row: CategoryRow, translations: TranslationMap | undefined, locales: string[]): string | null {
279
+ return localize(row.description, translations?.get(row.id), 'description', locales)
280
+ }
281
+
282
+ function childrenIndex(
283
+ visible: Map<string, CategoryRow>,
284
+ eligible: (row: CategoryRow) => boolean,
285
+ ): Map<string | null, CategoryRow[]> {
286
+ const index = new Map<string | null, CategoryRow[]>()
287
+ for (const row of visible.values()) {
288
+ if (!eligible(row)) continue
289
+ const parentKey = row.parentId !== null && visible.has(row.parentId) ? row.parentId : null
290
+ if (row.parentId !== null && parentKey === null) continue
291
+ const bucket = index.get(parentKey) ?? []
292
+ bucket.push(row)
293
+ index.set(parentKey, bucket)
294
+ }
295
+ return index
296
+ }
297
+
298
+ function collectTreeIds(
299
+ index: Map<string | null, CategoryRow[]>,
300
+ parentKey: string | null,
301
+ levels: number,
302
+ into: string[],
303
+ ): void {
304
+ if (levels <= 0) return
305
+ for (const row of index.get(parentKey) ?? []) {
306
+ into.push(row.id)
307
+ collectTreeIds(index, row.id, levels - 1, into)
308
+ }
309
+ }
310
+
311
+ /**
312
+ * `GET /categories`: the visible tree below `parentId` (the roots when omitted), `depth` levels
313
+ * deep (all levels when omitted). Empty categories are dropped unless `includeEmpty`; a node
314
+ * beyond `depth` is not expanded but still reports `hasChildren`. An unknown, hidden or
315
+ * out-of-assortment `parentId` yields an empty tree, exactly like a nonexistent one.
316
+ */
317
+ export async function getStorefrontCategoryTree(
318
+ container: AwilixContainer,
319
+ ctx: StoreContext,
320
+ query: Pick<EcommerceStorefrontCategoryTreeQuery, 'parentId' | 'depth' | 'includeEmpty'>,
321
+ ): Promise<StorefrontCategoryTreeResponse> {
322
+ const runtime = resolveRuntime(container, ctx)
323
+ const parentKey = query.parentId ?? null
324
+ const { visible, counts } = await loadCategorySnapshot(
325
+ runtime,
326
+ parentKey === null ? undefined : (candidates) => candidates.has(parentKey),
327
+ )
328
+ if (parentKey !== null && !visible.has(parentKey)) return { tree: [], effectiveLocale: ctx.effectiveLocale }
329
+ const includeEmpty = query.includeEmpty === true
330
+ const index = childrenIndex(visible, (row) => includeEmpty || isNonEmpty(counts, row.id))
331
+ const levels = query.depth ?? Number.POSITIVE_INFINITY
332
+ const ids: string[] = []
333
+ collectTreeIds(index, parentKey, levels, ids)
334
+ const translations = (
335
+ await loadStorefrontTranslations(runtime.em, ctx, [{ entityType: STOREFRONT_CATEGORY_ENTITY_TYPE, ids }])
336
+ ).get(STOREFRONT_CATEGORY_ENTITY_TYPE)
337
+ const locales = localeChain(ctx)
338
+
339
+ const build = (parent: string | null, remaining: number): StorefrontCategoryNode[] => {
340
+ if (remaining <= 0) return []
341
+ return (index.get(parent) ?? [])
342
+ .map((row) => ({ row, name: localizedName(row, translations, locales) }))
343
+ .sort((left, right) => compareNodes({ name: left.name, id: left.row.id }, { name: right.name, id: right.row.id }))
344
+ .map(({ row, name }) => ({
345
+ id: row.id,
346
+ name,
347
+ slug: row.slug,
348
+ description: localizedDescription(row, translations, locales),
349
+ depth: row.depth,
350
+ parentId: row.parentId,
351
+ productCount: productCountOf(counts, row.id),
352
+ hasChildren: (index.get(row.id)?.length ?? 0) > 0,
353
+ children: build(row.id, remaining - 1),
354
+ }))
355
+ }
356
+ return { tree: build(parentKey, levels), effectiveLocale: ctx.effectiveLocale }
357
+ }
358
+
359
+ /**
360
+ * The category block of `GET /categories/:slug`. `null` for a slug that matches no visible
361
+ * category — nonexistent, inactive, deleted, another tenant's, outside the buyer's assortment, or
362
+ * under an inactive ancestor — all through the same single categories query, so the three cases
363
+ * cannot be told apart by cost or by answer.
364
+ */
365
+ export async function getStorefrontCategoryLanding(
366
+ container: AwilixContainer,
367
+ ctx: StoreContext,
368
+ slug: string,
369
+ ): Promise<StorefrontCategoryLandingBlock | null> {
370
+ const runtime = resolveRuntime(container, ctx)
371
+ const findBySlug = (candidates: Map<string, CategoryRow>) =>
372
+ Array.from(candidates.values()).find((candidate) => candidate.slug === slug)
373
+ const { visible, counts } = await loadCategorySnapshot(runtime, (candidates) => findBySlug(candidates) !== undefined)
374
+ const row = findBySlug(visible)
375
+ if (!row) return null
376
+ const ancestors = row.ancestorIds.flatMap((id) => {
377
+ const ancestor = visible.get(id)
378
+ return ancestor ? [ancestor] : []
379
+ })
380
+ const children = Array.from(visible.values()).filter(
381
+ (candidate) => candidate.parentId === row.id && isNonEmpty(counts, candidate.id),
382
+ )
383
+ const translations = (
384
+ await loadStorefrontTranslations(runtime.em, ctx, [
385
+ {
386
+ entityType: STOREFRONT_CATEGORY_ENTITY_TYPE,
387
+ ids: [row.id, ...ancestors.map((ancestor) => ancestor.id), ...children.map((child) => child.id)],
388
+ },
389
+ ])
390
+ ).get(STOREFRONT_CATEGORY_ENTITY_TYPE)
391
+ const locales = localeChain(ctx)
392
+ return {
393
+ category: {
394
+ id: row.id,
395
+ name: localizedName(row, translations, locales),
396
+ slug: row.slug,
397
+ description: localizedDescription(row, translations, locales),
398
+ depth: row.depth,
399
+ parentId: row.parentId,
400
+ ancestorIds: ancestors.map((ancestor) => ancestor.id),
401
+ breadcrumb: [...ancestors, row].map((entry) => ({
402
+ id: entry.id,
403
+ name: localizedName(entry, translations, locales),
404
+ slug: entry.slug,
405
+ })),
406
+ children: children
407
+ .map((child) => ({
408
+ id: child.id,
409
+ name: localizedName(child, translations, locales),
410
+ slug: child.slug,
411
+ productCount: productCountOf(counts, child.id),
412
+ }))
413
+ .sort(compareNodes),
414
+ productCount: productCountOf(counts, row.id),
415
+ seo: { title: null, description: null, canonicalUrl: null },
416
+ },
417
+ effectiveLocale: ctx.effectiveLocale,
418
+ }
419
+ }
@@ -0,0 +1,113 @@
1
+ import type { AwilixContainer } from 'awilix'
2
+ import type { EcommerceStorefrontCategoryLandingQuery, EcommerceStorefrontCategoryTreeQuery } from '../data/validators'
3
+ import { catalogCategoryTag, catalogProductsTag, storefrontCache, storefrontCacheValueHash } from './cacheKeys'
4
+ import {
5
+ getStorefrontCategoryLanding,
6
+ getStorefrontCategoryTree,
7
+ type StorefrontCategoryLandingBlock,
8
+ type StorefrontCategoryTreeResponse,
9
+ } from './storefrontCategories'
10
+ import { cachedListStorefrontProducts } from './storefrontProductCache'
11
+ import type { StorefrontProductListResponse } from './storefrontProducts'
12
+ import type { StoreContext } from './types'
13
+
14
+ /**
15
+ * Server-side cache for `GET /categories` and `GET /categories/:slug` (Storefront Public API rev 4
16
+ * §9). Neither response carries a price, so the tree and the landing's category block are keyed on
17
+ * `assortmentScopeHash` (`scope: 'assortment'`: store, effective locale, hash) and shared by every
18
+ * buyer with the same effective assortment. The landing's embedded `/products` response is
19
+ * buyer-priced: it goes through `cachedListStorefrontProducts` on the full digest, and shares its
20
+ * entry with an identical `GET /products?categoryId=…` request.
21
+ *
22
+ * A cached category block is only ever stored for a category visible under that assortment, so a
23
+ * hit never needs the visibility check again; a miss that resolves to nothing is never cached, so
24
+ * a 404 costs the same on every request. Entries carry the tenant's `catalog-products` tag, which
25
+ * every category, product and price event evicts, plus the landing category's own tag.
26
+ */
27
+
28
+ export const STOREFRONT_CATEGORY_TREE_TTL_MS = 300_000
29
+ export const STOREFRONT_CATEGORY_LANDING_TTL_MS = 60_000
30
+
31
+ const TREE_CACHE_SEGMENT = 'categories-tree'
32
+ const LANDING_CACHE_SEGMENT = 'categories-landing'
33
+ const RESOLUTION_ONLY_PARAMETERS: ReadonlySet<string> = new Set(['path', 'storeSlug', 'locale'])
34
+
35
+ export function storefrontCategoryTreeCacheParts(
36
+ query: Pick<EcommerceStorefrontCategoryTreeQuery, 'parentId' | 'depth' | 'includeEmpty'>,
37
+ ): string[] {
38
+ const relevant = Object.fromEntries(
39
+ Object.entries(query).filter(([key]) => !RESOLUTION_ONLY_PARAMETERS.has(key)),
40
+ )
41
+ return [TREE_CACHE_SEGMENT, storefrontCacheValueHash(relevant)]
42
+ }
43
+
44
+ export function storefrontCategoryLandingCacheParts(slug: string): string[] {
45
+ return [LANDING_CACHE_SEGMENT, slug.trim()]
46
+ }
47
+
48
+ export function storefrontCategoryTreeCacheTags(ctx: StoreContext): string[] {
49
+ return [catalogProductsTag(ctx.tenantId)]
50
+ }
51
+
52
+ export function storefrontCategoryLandingCacheTags(ctx: StoreContext, block: StorefrontCategoryLandingBlock): string[] {
53
+ return [catalogProductsTag(ctx.tenantId), catalogCategoryTag(block.category.id)]
54
+ }
55
+
56
+ export async function cachedGetStorefrontCategoryTree(
57
+ container: AwilixContainer,
58
+ ctx: StoreContext,
59
+ query: Pick<EcommerceStorefrontCategoryTreeQuery, 'parentId' | 'depth' | 'includeEmpty'>,
60
+ ): Promise<StorefrontCategoryTreeResponse> {
61
+ const cache = storefrontCache(container, ctx)
62
+ const parts = storefrontCategoryTreeCacheParts(query)
63
+ const cached = await cache.get<StorefrontCategoryTreeResponse>(parts, { scope: 'assortment' })
64
+ if (cached) return cached
65
+ const response = await getStorefrontCategoryTree(container, ctx, query)
66
+ await cache.set(parts, response, {
67
+ scope: 'assortment',
68
+ ttlMs: STOREFRONT_CATEGORY_TREE_TTL_MS,
69
+ tags: storefrontCategoryTreeCacheTags(ctx),
70
+ })
71
+ return response
72
+ }
73
+
74
+ export type StorefrontCategoryLandingResponse = StorefrontCategoryLandingBlock & {
75
+ products: StorefrontProductListResponse
76
+ }
77
+
78
+ async function cachedGetStorefrontCategoryBlock(
79
+ container: AwilixContainer,
80
+ ctx: StoreContext,
81
+ slug: string,
82
+ ): Promise<StorefrontCategoryLandingBlock | null> {
83
+ const cache = storefrontCache(container, ctx)
84
+ const parts = storefrontCategoryLandingCacheParts(slug)
85
+ const cached = await cache.get<StorefrontCategoryLandingBlock>(parts, { scope: 'assortment' })
86
+ if (cached) return cached
87
+ const block = await getStorefrontCategoryLanding(container, ctx, slug)
88
+ if (!block) return null
89
+ await cache.set(parts, block, {
90
+ scope: 'assortment',
91
+ ttlMs: STOREFRONT_CATEGORY_LANDING_TTL_MS,
92
+ tags: storefrontCategoryLandingCacheTags(ctx, block),
93
+ })
94
+ return block
95
+ }
96
+
97
+ /**
98
+ * `null` when no visible category has this slug — and nothing about the embedded listing is touched
99
+ * then — or when the listing no longer resolves the category (it went inactive between the cached
100
+ * block and the listing), so a landing can never serve an unfiltered catalogue.
101
+ */
102
+ export async function cachedGetStorefrontCategoryLanding(
103
+ container: AwilixContainer,
104
+ ctx: StoreContext,
105
+ slug: string,
106
+ query: EcommerceStorefrontCategoryLandingQuery,
107
+ ): Promise<StorefrontCategoryLandingResponse | null> {
108
+ const block = await cachedGetStorefrontCategoryBlock(container, ctx, slug)
109
+ if (!block) return null
110
+ const products = await cachedListStorefrontProducts(container, ctx, { ...query, categoryId: block.category.id })
111
+ if (products.appliedFilters.category?.id !== block.category.id) return null
112
+ return { ...block, products }
113
+ }