@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,697 @@
1
+ import type { AwilixContainer } from 'awilix'
2
+ import type { EntityManager } from '@mikro-orm/postgresql'
3
+ import { sql } from 'kysely'
4
+ import type { AvailabilityState } from '@open-mercato/shared/lib/availability'
5
+ import type { EffectiveAssortmentScope } from '@open-mercato/shared/lib/catalog-visibility'
6
+ import { findWithDecryption } from '@open-mercato/shared/lib/encryption/find'
7
+ import { defaultLocale } from '@open-mercato/shared/lib/i18n/config'
8
+ import { resolveSupportedLocale } from '@open-mercato/shared/lib/i18n/locale'
9
+ import { loadDictionary } from '@open-mercato/shared/lib/i18n/server'
10
+ import { createFallbackTranslator } from '@open-mercato/shared/lib/i18n/translate'
11
+ import type { QueryEngine, Where } from '@open-mercato/shared/lib/query/types'
12
+ import { E } from '#generated/entities.ids.generated'
13
+ import {
14
+ CatalogOptionSchemaTemplate,
15
+ CatalogProductCategory,
16
+ CatalogProductVariant,
17
+ } from '@open-mercato/core/modules/catalog/data/entities'
18
+ import {
19
+ CATALOG_PRODUCT_TYPES,
20
+ type CatalogProductOptionDefinition,
21
+ } from '@open-mercato/core/modules/catalog/data/types'
22
+ import {
23
+ optionChoiceLabelTranslationField,
24
+ optionLabelTranslationField,
25
+ } from '@open-mercato/core/modules/catalog/lib/optionSchemaTranslations'
26
+ import { compareCodeUnits } from '@open-mercato/core/modules/catalog/lib/productScopeKeys'
27
+ import { catalogCategoryTag, catalogProductsTag, storefrontCache, storefrontCacheValueHash } from './cacheKeys'
28
+ import {
29
+ STOREFRONT_CATEGORY_ENTITY_TYPE,
30
+ STOREFRONT_OPTION_SCHEMA_ENTITY_TYPE,
31
+ STOREFRONT_TAG_ENTITY_TYPE,
32
+ isCategoryInAssortment,
33
+ localize,
34
+ nonEmptyString,
35
+ referenceId,
36
+ stringList,
37
+ type TranslationMap,
38
+ } from './storefrontCatalogSupport'
39
+ import type { StorefrontProductScope } from './storefrontProductScope'
40
+ import type { StoreContext } from './types'
41
+
42
+ /**
43
+ * Listing facets (Storefront Public API rev 4 §5.3, §5.4, §9.1).
44
+ *
45
+ * The four count facets (`categories`, `tags`, `options`, `productTypes`) are buyer-independent
46
+ * beyond the assortment, so they are computed once per assortment scope and cached under
47
+ * `assortmentScopeHash` (`scope: 'assortment'`). `priceRange` is buyer-priced and is computed and
48
+ * cached with the items on the full digest — never here. `availability` counts the returned page
49
+ * only (D21) and is computed by the listing.
50
+ *
51
+ * Cross-exclusion: the facet universe is the assortment scope plus the search term (the only
52
+ * non-facet product filter). Each dimension then applies every active filter except its own, in
53
+ * memory, over that universe — so with no filters every dimension shares one universe load, and
54
+ * with filters no dimension costs an extra query. Option codes are separate dimensions: the
55
+ * counts for `options[color]` keep `options[size]` applied (on the same variant) but not
56
+ * `options[color]`. The price filter is not applied to count facets — it depends on the buyer's
57
+ * prices, which the assortment-keyed entry must not.
58
+ *
59
+ * The universe is hard-capped at `STOREFRONT_FACET_UNIVERSE_CAP` products: past it the universe is
60
+ * never loaded and the count facets degrade to empty lists (§10 query budget over completeness).
61
+ */
62
+
63
+ export const STOREFRONT_COUNT_FACETS_TTL_MS = 30_000
64
+
65
+ const COUNT_FACETS_CACHE_SEGMENT = 'products-count-facets'
66
+ /** The most products a facet or category-count universe ever loads; larger universes are not counted. */
67
+ export const STOREFRONT_FACET_UNIVERSE_CAP = 10_000
68
+ const VARIANT_ID_CHUNK = 10_000
69
+ const CATALOG_PRODUCT_TYPE_LABEL_PREFIX = 'catalog.products.types.'
70
+
71
+ export type StorefrontFacets = {
72
+ categories: Array<{ id: string; name: string; slug: string | null; depth: number; parentId: string | null; count: number }>
73
+ tags: Array<{ slug: string; label: string; count: number }>
74
+ priceRange: { min: number; max: number; currencyCode: string } | null
75
+ options: Array<{ code: string; label: string; values: Array<{ code: string; label: string; count: number }> }>
76
+ productTypes: Array<{ type: string; label: string; count: number }>
77
+ availability: Array<{ state: AvailabilityState; count: number }>
78
+ availabilityScope: 'page'
79
+ total: number
80
+ }
81
+
82
+ export type StorefrontCountFacets = Pick<StorefrontFacets, 'categories' | 'tags' | 'options' | 'productTypes'>
83
+
84
+ /** The facet filters the listing actually applied (unknown or out-of-assortment selections already dropped). */
85
+ export type StorefrontFacetSelection = {
86
+ categoryId: string | null
87
+ tagIds: string[]
88
+ productType: string | null
89
+ options: Record<string, string[]>
90
+ }
91
+
92
+ export type StorefrontFacetRuntime = {
93
+ ctx: StoreContext
94
+ em: EntityManager
95
+ queryEngine: QueryEngine
96
+ scope: StorefrontProductScope
97
+ decryptionScope: { tenantId: string; organizationId: string }
98
+ }
99
+
100
+ export type StorefrontFacetProduct = {
101
+ id: string
102
+ productType: string
103
+ templateId: string | null
104
+ categoryIds: string[]
105
+ tagIds: string[]
106
+ }
107
+
108
+ export type StorefrontFacetVariant = { id: string; optionValues: Record<string, string> }
109
+
110
+ export type StorefrontFacetCategory = {
111
+ id: string
112
+ name: string
113
+ slug: string | null
114
+ depth: number
115
+ parentId: string | null
116
+ isActive: boolean
117
+ ancestorIds: string[]
118
+ descendantIds: string[]
119
+ }
120
+
121
+ export type StorefrontFacetTag = { id: string; slug: string; label: string }
122
+
123
+ export type StorefrontFacetSource = {
124
+ products: StorefrontFacetProduct[]
125
+ variants: Map<string, StorefrontFacetVariant[]>
126
+ categories: Map<string, StorefrontFacetCategory>
127
+ tags: Map<string, StorefrontFacetTag>
128
+ templates: Map<string, CatalogProductOptionDefinition[]>
129
+ }
130
+
131
+ export type StorefrontRawCountFacets = {
132
+ categories: Map<string, number>
133
+ tags: Map<string, number>
134
+ productTypes: Map<string, number>
135
+ options: Map<string, Map<string, number>>
136
+ }
137
+
138
+ type UniverseRecord = { id?: unknown; product_type?: unknown; option_schema_id?: unknown }
139
+
140
+ type FacetAssignmentDatabase = {
141
+ catalog_product_category_assignments: {
142
+ product_id: string
143
+ category_id: string
144
+ tenant_id: string
145
+ organization_id: string
146
+ }
147
+ catalog_product_tag_assignments: {
148
+ product_id: string
149
+ tag_id: string
150
+ tenant_id: string
151
+ organization_id: string
152
+ }
153
+ catalog_product_tags: {
154
+ id: string
155
+ slug: string
156
+ label: string
157
+ tenant_id: string
158
+ organization_id: string
159
+ }
160
+ }
161
+
162
+ type AssignmentRow = { product_id: string; kind: string; ref_id: string; slug: string | null; label: string | null }
163
+
164
+ function isRecord(value: unknown): value is Record<string, unknown> {
165
+ return typeof value === 'object' && value !== null && !Array.isArray(value)
166
+ }
167
+
168
+ function chunk<T>(values: T[], size: number): T[][] {
169
+ const chunks: T[][] = []
170
+ for (let index = 0; index < values.length; index += size) chunks.push(values.slice(index, index + size))
171
+ return chunks
172
+ }
173
+
174
+ function increment<K>(counts: Map<K, number>, key: K): void {
175
+ counts.set(key, (counts.get(key) ?? 0) + 1)
176
+ }
177
+
178
+ /**
179
+ * The count-facet key part: the resolved facet selection (unknown or out-of-assortment category
180
+ * and tag slugs already dropped; option values are validator-bounded, sorted and de-duplicated)
181
+ * plus the sanitized search term. Price,
182
+ * sort, paging, availability and locale are excluded (locale is already part of the assortment
183
+ * scope segment).
184
+ */
185
+ export function storefrontCountFacetCacheParts(selection: StorefrontFacetSelection, searchTerm: string | null): string[] {
186
+ const options: Record<string, string[]> = {}
187
+ for (const code of Object.keys(selection.options).sort(compareCodeUnits)) {
188
+ const values = Array.from(new Set(selection.options[code])).sort(compareCodeUnits)
189
+ if (values.length) options[code] = values
190
+ }
191
+ return [
192
+ COUNT_FACETS_CACHE_SEGMENT,
193
+ storefrontCacheValueHash({
194
+ search: searchTerm ?? null,
195
+ categoryId: selection.categoryId,
196
+ tagIds: Array.from(new Set(selection.tagIds)).sort(compareCodeUnits),
197
+ productType: selection.productType,
198
+ options,
199
+ }),
200
+ ]
201
+ }
202
+
203
+ export function storefrontCountFacetCacheTags(ctx: StoreContext, selection: Pick<StorefrontFacetSelection, 'categoryId'>): string[] {
204
+ return [catalogProductsTag(ctx.tenantId), ...(selection.categoryId ? [catalogCategoryTag(selection.categoryId)] : [])]
205
+ }
206
+
207
+ export async function readCachedStorefrontCountFacets(
208
+ container: AwilixContainer,
209
+ ctx: StoreContext,
210
+ selection: StorefrontFacetSelection,
211
+ searchTerm: string | null,
212
+ ): Promise<StorefrontCountFacets | null> {
213
+ return storefrontCache(container, ctx).get<StorefrontCountFacets>(storefrontCountFacetCacheParts(selection, searchTerm), {
214
+ scope: 'assortment',
215
+ })
216
+ }
217
+
218
+ export async function writeCachedStorefrontCountFacets(
219
+ container: AwilixContainer,
220
+ ctx: StoreContext,
221
+ selection: StorefrontFacetSelection,
222
+ searchTerm: string | null,
223
+ facets: StorefrontCountFacets,
224
+ ): Promise<void> {
225
+ await storefrontCache(container, ctx).set(storefrontCountFacetCacheParts(selection, searchTerm), facets, {
226
+ scope: 'assortment',
227
+ ttlMs: STOREFRONT_COUNT_FACETS_TTL_MS,
228
+ tags: storefrontCountFacetCacheTags(ctx, selection),
229
+ })
230
+ }
231
+
232
+ /**
233
+ * The scoped product ids of a facet or category-count universe, or `null` when it holds more than
234
+ * `cap` products — one query either way, never more than `cap` rows.
235
+ */
236
+ export async function queryStorefrontProductUniverse(
237
+ runtime: StorefrontFacetRuntime,
238
+ filters: Where,
239
+ cap: number = STOREFRONT_FACET_UNIVERSE_CAP,
240
+ ): Promise<UniverseRecord[] | null> {
241
+ const result = await runtime.queryEngine.query<UniverseRecord>(E.catalog.catalog_product, {
242
+ tenantId: runtime.scope.tenantId,
243
+ organizationId: runtime.scope.organizationId,
244
+ withDeleted: runtime.scope.withDeleted,
245
+ filters,
246
+ fields: ['id', 'product_type', 'option_schema_id'],
247
+ page: { page: 1, pageSize: Math.max(1, cap) },
248
+ })
249
+ return result.total > result.items.length ? null : result.items
250
+ }
251
+
252
+ async function loadAssignments(runtime: StorefrontFacetRuntime, productIds: string[]): Promise<AssignmentRow[]> {
253
+ const { tenantId, organizationId } = runtime.decryptionScope
254
+ const db = runtime.em.getKysely<FacetAssignmentDatabase>()
255
+ const categoryRows = db
256
+ .selectFrom('catalog_product_category_assignments as ca')
257
+ .select([
258
+ 'ca.product_id as product_id',
259
+ sql<string>`'category'`.as('kind'),
260
+ 'ca.category_id as ref_id',
261
+ sql<string | null>`null`.as('slug'),
262
+ sql<string | null>`null`.as('label'),
263
+ ])
264
+ .where(sql<boolean>`${sql.ref('ca.product_id')} = any(${productIds}::uuid[])`)
265
+ .where('ca.tenant_id', '=', tenantId)
266
+ .where('ca.organization_id', '=', organizationId)
267
+ const tagRows = db
268
+ .selectFrom('catalog_product_tag_assignments as ta')
269
+ .innerJoin('catalog_product_tags as tg', 'tg.id', 'ta.tag_id')
270
+ .select([
271
+ 'ta.product_id as product_id',
272
+ sql<string>`'tag'`.as('kind'),
273
+ 'ta.tag_id as ref_id',
274
+ sql<string | null>`${sql.ref('tg.slug')}`.as('slug'),
275
+ sql<string | null>`${sql.ref('tg.label')}`.as('label'),
276
+ ])
277
+ .where(sql<boolean>`${sql.ref('ta.product_id')} = any(${productIds}::uuid[])`)
278
+ .where('ta.tenant_id', '=', tenantId)
279
+ .where('ta.organization_id', '=', organizationId)
280
+ .where('tg.tenant_id', '=', tenantId)
281
+ .where('tg.organization_id', '=', organizationId)
282
+ return categoryRows.unionAll(tagRows).execute()
283
+ }
284
+
285
+ async function loadCategories(runtime: StorefrontFacetRuntime): Promise<Map<string, StorefrontFacetCategory>> {
286
+ const { ctx } = runtime
287
+ const rows = await findWithDecryption(
288
+ runtime.em,
289
+ CatalogProductCategory,
290
+ { tenantId: ctx.tenantId, organizationId: ctx.organizationId, deletedAt: null },
291
+ { fields: ['id', 'name', 'slug', 'depth', 'parentId', 'isActive', 'ancestorIds', 'descendantIds'] },
292
+ runtime.decryptionScope,
293
+ )
294
+ return new Map(
295
+ rows.map((row) => [
296
+ row.id,
297
+ {
298
+ id: row.id,
299
+ name: row.name,
300
+ slug: row.slug ?? null,
301
+ depth: typeof row.depth === 'number' ? row.depth : 0,
302
+ parentId: row.parentId ?? null,
303
+ isActive: row.isActive !== false,
304
+ ancestorIds: stringList(row.ancestorIds),
305
+ descendantIds: stringList(row.descendantIds),
306
+ },
307
+ ]),
308
+ )
309
+ }
310
+
311
+ async function loadVariants(
312
+ runtime: StorefrontFacetRuntime,
313
+ productIds: string[],
314
+ ): Promise<Map<string, StorefrontFacetVariant[]>> {
315
+ const { ctx } = runtime
316
+ const batches = await Promise.all(
317
+ chunk(productIds, VARIANT_ID_CHUNK).map((ids) =>
318
+ findWithDecryption(
319
+ runtime.em,
320
+ CatalogProductVariant,
321
+ { tenantId: ctx.tenantId, organizationId: ctx.organizationId, deletedAt: null, isActive: true, product: { $in: ids } },
322
+ { fields: ['id', 'product', 'optionValues'] },
323
+ runtime.decryptionScope,
324
+ ),
325
+ ),
326
+ )
327
+ const byProduct = new Map<string, StorefrontFacetVariant[]>()
328
+ for (const variant of batches.flat()) {
329
+ const productId = referenceId(variant.product)
330
+ if (!productId) continue
331
+ const optionValues: Record<string, string> = {}
332
+ if (isRecord(variant.optionValues)) {
333
+ for (const [code, value] of Object.entries(variant.optionValues)) {
334
+ const text = nonEmptyString(value)
335
+ if (text !== null) optionValues[code] = text
336
+ }
337
+ }
338
+ const bucket = byProduct.get(productId) ?? []
339
+ bucket.push({ id: variant.id, optionValues })
340
+ byProduct.set(productId, bucket)
341
+ }
342
+ return byProduct
343
+ }
344
+
345
+ async function loadTemplates(
346
+ runtime: StorefrontFacetRuntime,
347
+ templateIds: string[],
348
+ ): Promise<Map<string, CatalogProductOptionDefinition[]>> {
349
+ const templates = new Map<string, CatalogProductOptionDefinition[]>()
350
+ if (!templateIds.length) return templates
351
+ const { ctx } = runtime
352
+ const rows = await findWithDecryption(
353
+ runtime.em,
354
+ CatalogOptionSchemaTemplate,
355
+ { id: { $in: templateIds }, tenantId: ctx.tenantId, organizationId: ctx.organizationId, deletedAt: null, isActive: true },
356
+ { fields: ['id', 'schema'] },
357
+ runtime.decryptionScope,
358
+ )
359
+ for (const row of rows) {
360
+ const options = isRecord(row.schema) && Array.isArray(row.schema.options) ? row.schema.options : []
361
+ templates.set(
362
+ row.id,
363
+ options.filter((option): option is CatalogProductOptionDefinition => isRecord(option) && nonEmptyString(option.code) !== null),
364
+ )
365
+ }
366
+ return templates
367
+ }
368
+
369
+ /**
370
+ * The facet universe and everything the count facets read: the scoped products (one query-engine
371
+ * call composing `filters`, normally the assortment scope plus the search clause), their category
372
+ * and tag assignments (one union query), the tenant's categories, the products' active variants
373
+ * and the option schema templates they use. Five queries for a universe of at most `cap` products;
374
+ * `null` after the single universe query when it is larger. The variant list doubles as the
375
+ * listing's variant index.
376
+ */
377
+ export async function loadStorefrontFacetSource(
378
+ runtime: StorefrontFacetRuntime,
379
+ filters: Where,
380
+ cap: number = STOREFRONT_FACET_UNIVERSE_CAP,
381
+ ): Promise<StorefrontFacetSource | null> {
382
+ const records = await queryStorefrontProductUniverse(runtime, filters, cap)
383
+ if (!records) return null
384
+ const products = new Map<string, StorefrontFacetProduct>()
385
+ for (const record of records) {
386
+ if (typeof record.id !== 'string' || products.has(record.id)) continue
387
+ products.set(record.id, {
388
+ id: record.id,
389
+ productType: nonEmptyString(record.product_type) ?? 'simple',
390
+ templateId: nonEmptyString(record.option_schema_id),
391
+ categoryIds: [],
392
+ tagIds: [],
393
+ })
394
+ }
395
+ const productIds = Array.from(products.keys())
396
+ if (!productIds.length) {
397
+ return { products: [], variants: new Map(), categories: new Map(), tags: new Map(), templates: new Map() }
398
+ }
399
+ const templateIds = Array.from(
400
+ new Set(Array.from(products.values()).flatMap((product) => (product.templateId ? [product.templateId] : []))),
401
+ )
402
+ const [assignments, categories, variants, templates] = await Promise.all([
403
+ loadAssignments(runtime, productIds),
404
+ loadCategories(runtime),
405
+ loadVariants(runtime, productIds),
406
+ loadTemplates(runtime, templateIds),
407
+ ])
408
+ const tags = new Map<string, StorefrontFacetTag>()
409
+ for (const row of assignments) {
410
+ const product = products.get(String(row.product_id))
411
+ const refId = String(row.ref_id)
412
+ if (!product) continue
413
+ if (row.kind === 'category') {
414
+ if (!product.categoryIds.includes(refId)) product.categoryIds.push(refId)
415
+ continue
416
+ }
417
+ if (!product.tagIds.includes(refId)) product.tagIds.push(refId)
418
+ if (!tags.has(refId)) tags.set(refId, { id: refId, slug: row.slug ?? '', label: row.label ?? '' })
419
+ }
420
+ return { products: Array.from(products.values()), variants, categories, tags, templates }
421
+ }
422
+
423
+ export function storefrontFacetVariantIndex(source: StorefrontFacetSource): Map<string, string[]> {
424
+ const index = new Map<string, string[]>()
425
+ for (const product of source.products) {
426
+ index.set(product.id, (source.variants.get(product.id) ?? []).map((variant) => variant.id))
427
+ }
428
+ return index
429
+ }
430
+
431
+ function variantMatches(
432
+ variant: StorefrontFacetVariant,
433
+ options: Record<string, string[]>,
434
+ exceptCode: string | null,
435
+ ): boolean {
436
+ return Object.entries(options).every(([code, values]) => {
437
+ if (code === exceptCode) return true
438
+ const value = variant.optionValues[code]
439
+ return value !== undefined && values.includes(value)
440
+ })
441
+ }
442
+
443
+ /**
444
+ * Cross-facet exclusion (§5.4) over the facet universe: each dimension counts the products that
445
+ * match every active filter except its own. Category counts include descendants (a product counts
446
+ * for a category when it is assigned to it or to one of its descendants, as the filter does); option
447
+ * counts are per option code and count a product once per value carried by any of its variants that
448
+ * also matches the other option filters.
449
+ */
450
+ export function countStorefrontFacets(
451
+ source: StorefrontFacetSource,
452
+ selection: StorefrontFacetSelection,
453
+ ): StorefrontRawCountFacets {
454
+ const selectedCategory = selection.categoryId ? source.categories.get(selection.categoryId) : undefined
455
+ const categoryReach = selection.categoryId
456
+ ? new Set([selection.categoryId, ...(selectedCategory?.descendantIds ?? [])])
457
+ : null
458
+ const tagSet = selection.tagIds.length ? new Set(selection.tagIds) : null
459
+ const optionCodes = Object.keys(selection.options)
460
+ const variantsOf = (product: StorefrontFacetProduct) => source.variants.get(product.id) ?? []
461
+
462
+ const inCategory = (product: StorefrontFacetProduct) =>
463
+ !categoryReach || product.categoryIds.some((id) => categoryReach.has(id))
464
+ const inTags = (product: StorefrontFacetProduct) => !tagSet || product.tagIds.some((id) => tagSet.has(id))
465
+ const inType = (product: StorefrontFacetProduct) =>
466
+ !selection.productType || product.productType === selection.productType
467
+ const inOptions = (product: StorefrontFacetProduct) =>
468
+ optionCodes.length === 0 || variantsOf(product).some((variant) => variantMatches(variant, selection.options, null))
469
+
470
+ const contributors = new Map<string, string[]>()
471
+ for (const category of source.categories.values()) {
472
+ for (const id of [category.id, ...category.descendantIds]) {
473
+ const bucket = contributors.get(id) ?? []
474
+ bucket.push(category.id)
475
+ contributors.set(id, bucket)
476
+ }
477
+ }
478
+
479
+ const raw: StorefrontRawCountFacets = {
480
+ categories: new Map(),
481
+ tags: new Map(),
482
+ productTypes: new Map(),
483
+ options: new Map(),
484
+ }
485
+ const codes = new Set<string>(optionCodes)
486
+ for (const product of source.products) {
487
+ for (const variant of variantsOf(product)) for (const code of Object.keys(variant.optionValues)) codes.add(code)
488
+ }
489
+
490
+ for (const product of source.products) {
491
+ const category = inCategory(product)
492
+ const tags = inTags(product)
493
+ const type = inType(product)
494
+ const options = inOptions(product)
495
+ if (tags && type && options) {
496
+ const reached = new Set<string>()
497
+ for (const assignedId of product.categoryIds) {
498
+ for (const categoryId of contributors.get(assignedId) ?? []) reached.add(categoryId)
499
+ }
500
+ for (const categoryId of reached) increment(raw.categories, categoryId)
501
+ }
502
+ if (category && type && options) {
503
+ for (const tagId of product.tagIds) increment(raw.tags, tagId)
504
+ }
505
+ if (category && tags && options) increment(raw.productTypes, product.productType)
506
+ if (!(category && tags && type)) continue
507
+ for (const code of codes) {
508
+ const values = new Set<string>()
509
+ for (const variant of variantsOf(product)) {
510
+ const value = variant.optionValues[code]
511
+ if (value === undefined || !variantMatches(variant, selection.options, code)) continue
512
+ values.add(value)
513
+ }
514
+ if (!values.size) continue
515
+ const counts = raw.options.get(code) ?? new Map<string, number>()
516
+ for (const value of values) increment(counts, value)
517
+ raw.options.set(code, counts)
518
+ }
519
+ }
520
+ return raw
521
+ }
522
+
523
+ function sortedTemplateIds(source: StorefrontFacetSource): string[] {
524
+ return Array.from(source.templates.keys()).sort(compareCodeUnits)
525
+ }
526
+
527
+ /** The overlays the count-facet labels read, for the listing's single translation query (§7.2). */
528
+ export function storefrontFacetTranslationRequests(
529
+ source: StorefrontFacetSource,
530
+ raw: StorefrontRawCountFacets,
531
+ ): Array<{ entityType: string; ids: string[] }> {
532
+ return [
533
+ { entityType: STOREFRONT_CATEGORY_ENTITY_TYPE, ids: Array.from(raw.categories.keys()) },
534
+ { entityType: STOREFRONT_TAG_ENTITY_TYPE, ids: Array.from(raw.tags.keys()) },
535
+ { entityType: STOREFRONT_OPTION_SCHEMA_ENTITY_TYPE, ids: sortedTemplateIds(source) },
536
+ ]
537
+ }
538
+
539
+ export type StorefrontFacetLabelContext = {
540
+ locales: string[]
541
+ translations: Map<string, TranslationMap>
542
+ assortmentScope: EffectiveAssortmentScope
543
+ productTypeLabel: (type: string) => string
544
+ }
545
+
546
+ type OptionDefinitionRef = { templateId: string; option: CatalogProductOptionDefinition }
547
+
548
+ function optionDefinitions(source: StorefrontFacetSource): Map<string, OptionDefinitionRef> {
549
+ const definitions = new Map<string, OptionDefinitionRef>()
550
+ for (const templateId of sortedTemplateIds(source)) {
551
+ for (const option of source.templates.get(templateId) ?? []) {
552
+ if (!definitions.has(option.code)) definitions.set(option.code, { templateId, option })
553
+ }
554
+ }
555
+ return definitions
556
+ }
557
+
558
+ function compareLabels(left: { label: string; code: string }, right: { label: string; code: string }): number {
559
+ return left.label.localeCompare(right.label) || compareCodeUnits(left.code, right.code)
560
+ }
561
+
562
+ function productTypeRank(type: string): number {
563
+ const index = (CATALOG_PRODUCT_TYPES as readonly string[]).indexOf(type)
564
+ return index === -1 ? CATALOG_PRODUCT_TYPES.length : index
565
+ }
566
+
567
+ /**
568
+ * Turns raw counts into the payload: categories that are active, under active ancestors and inside the buyer's assortment,
569
+ * with localized names (depth, then name order); tags with localized labels (count, then label);
570
+ * options with the template's translated option and choice labels (choices in template order;
571
+ * values no template defines fall back to the stored value); product types with catalog labels.
572
+ * Only entries with a nonzero count are listed.
573
+ */
574
+ export function labelStorefrontCountFacets(
575
+ source: StorefrontFacetSource,
576
+ raw: StorefrontRawCountFacets,
577
+ context: StorefrontFacetLabelContext,
578
+ ): StorefrontCountFacets {
579
+ const { locales, translations } = context
580
+ const categoryTranslations = translations.get(STOREFRONT_CATEGORY_ENTITY_TYPE)
581
+ const tagTranslations = translations.get(STOREFRONT_TAG_ENTITY_TYPE)
582
+ const templateTranslations = translations.get(STOREFRONT_OPTION_SCHEMA_ENTITY_TYPE)
583
+
584
+ const categories: StorefrontCountFacets['categories'] = []
585
+ for (const [id, count] of raw.categories) {
586
+ const category = source.categories.get(id)
587
+ if (!category || !category.isActive || count <= 0) continue
588
+ if (!category.ancestorIds.every((ancestorId) => source.categories.get(ancestorId)?.isActive === true)) continue
589
+ const lineage = { id, ancestorIds: category.ancestorIds, descendantIds: category.descendantIds }
590
+ if (!isCategoryInAssortment(lineage, context.assortmentScope)) continue
591
+ categories.push({
592
+ id,
593
+ name: localize(category.name, categoryTranslations?.get(id), 'name', locales) ?? category.name,
594
+ slug: category.slug,
595
+ depth: category.depth,
596
+ parentId: category.parentId,
597
+ count,
598
+ })
599
+ }
600
+ categories.sort(
601
+ (left, right) => left.depth - right.depth || left.name.localeCompare(right.name) || compareCodeUnits(left.id, right.id),
602
+ )
603
+
604
+ const tags: StorefrontCountFacets['tags'] = []
605
+ for (const [id, count] of raw.tags) {
606
+ const tag = source.tags.get(id)
607
+ if (!tag || !tag.slug || count <= 0) continue
608
+ tags.push({ slug: tag.slug, label: localize(tag.label, tagTranslations?.get(id), 'label', locales) ?? tag.slug, count })
609
+ }
610
+ tags.sort(
611
+ (left, right) => right.count - left.count || left.label.localeCompare(right.label) || compareCodeUnits(left.slug, right.slug),
612
+ )
613
+
614
+ const definitions = optionDefinitions(source)
615
+ const options: StorefrontCountFacets['options'] = []
616
+ for (const [code, counts] of raw.options) {
617
+ const definition = definitions.get(code)
618
+ const overlay = definition ? templateTranslations?.get(definition.templateId) : undefined
619
+ const choices = Array.isArray(definition?.option.choices) ? definition.option.choices : []
620
+ const values: Array<{ code: string; label: string; count: number; rank: number }> = []
621
+ for (const [value, count] of counts) {
622
+ if (count <= 0) continue
623
+ let rank = choices.findIndex((choice) => choice.code === value)
624
+ if (rank === -1) rank = choices.findIndex((choice) => choice.label === value)
625
+ const choice = rank === -1 ? null : choices[rank]
626
+ const label = choice
627
+ ? localize(choice.label, overlay, optionChoiceLabelTranslationField(code, choice.code), locales) ?? value
628
+ : value
629
+ values.push({ code: value, label, count, rank: rank === -1 ? choices.length : rank })
630
+ }
631
+ if (!values.length) continue
632
+ values.sort((left, right) => left.rank - right.rank || compareLabels(left, right))
633
+ options.push({
634
+ code,
635
+ label: definition
636
+ ? localize(definition.option.label, overlay, optionLabelTranslationField(code), locales) ?? code
637
+ : code,
638
+ values: values.map((value) => ({ code: value.code, label: value.label, count: value.count })),
639
+ })
640
+ }
641
+ options.sort(compareLabels)
642
+
643
+ const productTypes: StorefrontCountFacets['productTypes'] = []
644
+ for (const [type, count] of raw.productTypes) {
645
+ if (count <= 0) continue
646
+ productTypes.push({ type, label: context.productTypeLabel(type), count })
647
+ }
648
+ productTypes.sort(
649
+ (left, right) => productTypeRank(left.type) - productTypeRank(right.type) || compareCodeUnits(left.type, right.type),
650
+ )
651
+
652
+ return { categories, tags, options, productTypes }
653
+ }
654
+
655
+ /** Catalog product-type labels (`catalog.products.types.<type>`) in the response locale; the type code when untranslated. */
656
+ export async function loadStorefrontProductTypeLabeler(locale: string): Promise<(type: string) => string> {
657
+ const translate = createFallbackTranslator(await loadDictionary(resolveSupportedLocale(locale) ?? defaultLocale))
658
+ return (type) => translate(`${CATALOG_PRODUCT_TYPE_LABEL_PREFIX}${type}`, type)
659
+ }
660
+
661
+ /** `priceRange` over the amounts the price filter compares against (each product's from-price). */
662
+ export function storefrontPriceRangeFacet(
663
+ amounts: Iterable<number | null>,
664
+ currencyCode: string,
665
+ ): StorefrontFacets['priceRange'] {
666
+ let min: number | null = null
667
+ let max: number | null = null
668
+ for (const amount of amounts) {
669
+ if (amount === null || !Number.isFinite(amount)) continue
670
+ if (min === null || amount < min) min = amount
671
+ if (max === null || amount > max) max = amount
672
+ }
673
+ return min === null || max === null ? null : { min, max, currencyCode }
674
+ }
675
+
676
+ const AVAILABILITY_STATE_ORDER: readonly AvailabilityState[] = [
677
+ 'in_stock',
678
+ 'low_stock',
679
+ 'backorder',
680
+ 'preorder',
681
+ 'not_tracked',
682
+ 'out_of_stock',
683
+ ]
684
+
685
+ function availabilityRank(state: AvailabilityState): number {
686
+ const index = AVAILABILITY_STATE_ORDER.indexOf(state)
687
+ return index === -1 ? AVAILABILITY_STATE_ORDER.length : index
688
+ }
689
+
690
+ /** Page-scoped availability counts (D21): the states of the returned page before the availability filter. */
691
+ export function storefrontAvailabilityFacet(states: Iterable<AvailabilityState>): StorefrontFacets['availability'] {
692
+ const counts = new Map<AvailabilityState, number>()
693
+ for (const state of states) increment(counts, state)
694
+ return Array.from(counts.entries())
695
+ .map(([state, count]) => ({ state, count }))
696
+ .sort((left, right) => availabilityRank(left.state) - availabilityRank(right.state))
697
+ }