@open-mercato/core 0.9.1-develop.7473.1.de5b22888f → 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,7 @@
1
+ {
2
+ "version": 3,
3
+ "sources": ["../../../../src/modules/ecommerce/lib/storeNotifications.ts"],
4
+ "sourcesContent": ["import { buildFeatureNotificationFromType } from '../../notifications/lib/notificationBuilder'\nimport type { NotificationService } from '../../notifications/lib/notificationService'\nimport { E } from '#generated/entities.ids.generated'\nimport { notificationTypes } from '../notifications'\nimport {\n asPayloadRecord,\n readEventScope,\n readPayloadString,\n tryResolveService,\n type EcommerceSubscriberContext,\n} from './subscriberSupport'\n\n/**\n * Store-manager alerts (SPEC-029 \u00A79.4) delivered through `NotificationService.createForFeature`.\n * Emitters already throttle per hour; the `groupKey` (store + UTC hour) additionally makes a\n * retried or duplicated delivery refresh the recipient's existing notification instead of adding one.\n */\n\nexport const STORE_NOTIFICATION_FEATURE = 'ecommerce.stores.manage'\n\nexport type StoreNotificationType = 'ecommerce.store.channel_binding_missing' | 'ecommerce.store.assortment_empty'\n\ntype FeatureNotifier = Pick<NotificationService, 'createForFeature'>\n\nexport function storeNotificationGroupKey(storeId: string, now: Date): string {\n return `${storeId}:${now.toISOString().slice(0, 13)}`\n}\n\nexport async function notifyStoreManagers(\n payload: unknown,\n ctx: EcommerceSubscriberContext,\n type: StoreNotificationType,\n now: Date = new Date(),\n): Promise<boolean> {\n const record = asPayloadRecord(payload)\n const storeId = readPayloadString(record, 'storeId') ?? readPayloadString(record, 'id')\n const { tenantId, organizationId } = readEventScope(record, ctx)\n if (!storeId || !tenantId || !organizationId) return false\n const notificationService = tryResolveService<FeatureNotifier>(ctx, 'notificationService')\n if (!notificationService) return false\n const typeDef = notificationTypes.find((definition) => definition.type === type)\n if (!typeDef) return false\n const input = buildFeatureNotificationFromType(typeDef, {\n requiredFeature: STORE_NOTIFICATION_FEATURE,\n sourceEntityType: E.ecommerce.ecommerce_store,\n sourceEntityId: storeId,\n linkHref: `/backend/config/ecommerce/${encodeURIComponent(storeId)}`,\n groupKey: storeNotificationGroupKey(storeId, now),\n })\n await notificationService.createForFeature(\n { ...input, restrictRecipientsToOrganization: true },\n { tenantId, organizationId },\n )\n return true\n}\n"],
5
+ "mappings": "AAAA,SAAS,wCAAwC;AAEjD,SAAS,SAAS;AAClB,SAAS,yBAAyB;AAClC;AAAA,EACE;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,OAEK;AAQA,MAAM,6BAA6B;AAMnC,SAAS,0BAA0B,SAAiB,KAAmB;AAC5E,SAAO,GAAG,OAAO,IAAI,IAAI,YAAY,EAAE,MAAM,GAAG,EAAE,CAAC;AACrD;AAEA,eAAsB,oBACpB,SACA,KACA,MACA,MAAY,oBAAI,KAAK,GACH;AAClB,QAAM,SAAS,gBAAgB,OAAO;AACtC,QAAM,UAAU,kBAAkB,QAAQ,SAAS,KAAK,kBAAkB,QAAQ,IAAI;AACtF,QAAM,EAAE,UAAU,eAAe,IAAI,eAAe,QAAQ,GAAG;AAC/D,MAAI,CAAC,WAAW,CAAC,YAAY,CAAC,eAAgB,QAAO;AACrD,QAAM,sBAAsB,kBAAmC,KAAK,qBAAqB;AACzF,MAAI,CAAC,oBAAqB,QAAO;AACjC,QAAM,UAAU,kBAAkB,KAAK,CAAC,eAAe,WAAW,SAAS,IAAI;AAC/E,MAAI,CAAC,QAAS,QAAO;AACrB,QAAM,QAAQ,iCAAiC,SAAS;AAAA,IACtD,iBAAiB;AAAA,IACjB,kBAAkB,EAAE,UAAU;AAAA,IAC9B,gBAAgB;AAAA,IAChB,UAAU,6BAA6B,mBAAmB,OAAO,CAAC;AAAA,IAClE,UAAU,0BAA0B,SAAS,GAAG;AAAA,EAClD,CAAC;AACD,QAAM,oBAAoB;AAAA,IACxB,EAAE,GAAG,OAAO,kCAAkC,KAAK;AAAA,IACnD,EAAE,UAAU,eAAe;AAAA,EAC7B;AACA,SAAO;AACT;",
6
+ "names": []
7
+ }
@@ -0,0 +1,248 @@
1
+ import { sql } from "kysely";
2
+ import {
3
+ availabilityItemKey,
4
+ resolveAvailability
5
+ } from "@open-mercato/shared/lib/availability";
6
+ import { parseBooleanFromUnknown } from "@open-mercato/shared/lib/boolean";
7
+ import { findWithDecryption } from "@open-mercato/shared/lib/encryption/find";
8
+ import { createLogger } from "@open-mercato/shared/lib/logger";
9
+ import { getTelemetryRuntime } from "@open-mercato/shared/lib/telemetry/runtime";
10
+ import { AttachmentPartition } from "@open-mercato/core/modules/attachments/data/entities";
11
+ import { CatalogProductCategory } from "@open-mercato/core/modules/catalog/data/entities";
12
+ import { batchLoadTranslationsMany } from "@open-mercato/core/modules/translations/lib/batch";
13
+ const logger = createLogger("ecommerce");
14
+ const STOREFRONT_PRODUCT_ENTITY_TYPE = "catalog:catalog_product";
15
+ const STOREFRONT_VARIANT_ENTITY_TYPE = "catalog:catalog_product_variant";
16
+ const STOREFRONT_CATEGORY_ENTITY_TYPE = "catalog:catalog_product_category";
17
+ const STOREFRONT_TAG_ENTITY_TYPE = "catalog:catalog_product_tag";
18
+ const STOREFRONT_OPTION_SCHEMA_ENTITY_TYPE = "catalog:catalog_option_schema_template";
19
+ function tryResolve(container, name) {
20
+ try {
21
+ return container.resolve(name) ?? null;
22
+ } catch {
23
+ return null;
24
+ }
25
+ }
26
+ function referenceId(value) {
27
+ if (typeof value === "string") return value.length ? value : null;
28
+ if (value && typeof value === "object" && "id" in value) {
29
+ const id = value.id;
30
+ return typeof id === "string" && id.length ? id : null;
31
+ }
32
+ return null;
33
+ }
34
+ function stringList(value) {
35
+ return Array.isArray(value) ? value.filter((entry) => typeof entry === "string" && entry.length > 0) : [];
36
+ }
37
+ function nonEmptyString(value) {
38
+ return typeof value === "string" && value.trim().length > 0 ? value : null;
39
+ }
40
+ function categoryBranchAdmits(branch, lineage, reach) {
41
+ const included = stringList(branch.categoryIds);
42
+ if (included.length > 0 && !included.some((id) => reach.has(id))) return false;
43
+ if (stringList(branch.excludeCategoryIds).some((id) => lineage.has(id))) return false;
44
+ return (branch.allOf ?? []).every((nested) => categoryBranchAdmits(nested, lineage, reach));
45
+ }
46
+ function isCategoryInAssortment(category, scope) {
47
+ if (scope === null) return true;
48
+ const lineage = /* @__PURE__ */ new Set([category.id, ...category.ancestorIds]);
49
+ const reach = /* @__PURE__ */ new Set([...lineage, ...category.descendantIds]);
50
+ return scope.some((branch) => categoryBranchAdmits(branch, lineage, reach));
51
+ }
52
+ async function selectCategoriesWithActiveAncestors(em, ctx, categories, known = {}) {
53
+ const ancestors = new Map(known.ancestors ?? []);
54
+ const active = /* @__PURE__ */ new Set([...known.activeIds ?? [], ...ancestors.keys()]);
55
+ const unknown = Array.from(
56
+ new Set(categories.flatMap((category) => category.ancestorIds.filter((id) => !active.has(id))))
57
+ );
58
+ if (unknown.length > 0) {
59
+ const rows = await findWithDecryption(
60
+ em,
61
+ CatalogProductCategory,
62
+ { id: { $in: unknown }, tenantId: ctx.tenantId, organizationId: ctx.organizationId, deletedAt: null, isActive: true },
63
+ { fields: ["id", "name", "slug", "ancestorIds"] },
64
+ { tenantId: ctx.tenantId, organizationId: ctx.organizationId }
65
+ );
66
+ for (const row of rows) {
67
+ active.add(row.id);
68
+ ancestors.set(row.id, { id: row.id, name: row.name, slug: row.slug ?? null, ancestorIds: stringList(row.ancestorIds) });
69
+ }
70
+ }
71
+ const visibleIds = new Set(
72
+ categories.filter((category) => category.ancestorIds.every((id) => active.has(id))).map((category) => category.id)
73
+ );
74
+ return { visibleIds, ancestors };
75
+ }
76
+ function localize(base, translations, field, locales) {
77
+ for (const locale of locales) {
78
+ const value = nonEmptyString(translations?.[locale]?.[field]);
79
+ if (value !== null) return value;
80
+ }
81
+ return nonEmptyString(base);
82
+ }
83
+ function localeChain(ctx, preferredLocale) {
84
+ const head = preferredLocale && ctx.store.supportedLocales.includes(preferredLocale) ? preferredLocale : ctx.effectiveLocale;
85
+ return Array.from(new Set([head, ctx.store.defaultLocale].filter((locale) => !!locale)));
86
+ }
87
+ function toStorefrontAvailability(result) {
88
+ if (!result) return { state: "not_tracked", canFulfil: true, leadTimeDays: null, releaseAt: null };
89
+ return {
90
+ state: result.state,
91
+ canFulfil: result.canFulfil,
92
+ leadTimeDays: result.leadTimeDays,
93
+ releaseAt: result.releaseAt
94
+ };
95
+ }
96
+ function storefrontAvailabilityKey(target) {
97
+ return availabilityItemKey({ catalogProductId: target.productId, catalogVariantId: target.variantId });
98
+ }
99
+ async function resolveStorefrontAvailability(container, ctx, em, targets) {
100
+ const results = /* @__PURE__ */ new Map();
101
+ if (!targets.length) return results;
102
+ const items = targets.map((target) => ({
103
+ catalogProductId: target.productId,
104
+ catalogVariantId: target.variantId,
105
+ quantity: 1
106
+ }));
107
+ const moduleConfig = tryResolve(container, "moduleConfigService");
108
+ const policyResolver = tryResolve(container, "policyResolutionService");
109
+ const [availability, policies] = await Promise.all([
110
+ resolveAvailability(
111
+ {
112
+ tenantId: ctx.tenantId,
113
+ organizationId: ctx.organizationId,
114
+ storeId: ctx.store.id,
115
+ channelId: ctx.channel?.salesChannelId ?? null,
116
+ items
117
+ },
118
+ moduleConfig ? { moduleConfig, container } : { container }
119
+ ),
120
+ policyResolver ? policyResolver.resolveMany(
121
+ em,
122
+ targets.map((target) => ({
123
+ tenantId: ctx.tenantId,
124
+ organizationId: ctx.organizationId,
125
+ storeId: ctx.store.id,
126
+ productId: target.productId,
127
+ variantId: target.variantId
128
+ }))
129
+ ) : Promise.resolve([])
130
+ ]);
131
+ targets.forEach((target, index) => {
132
+ const key = storefrontAvailabilityKey(target);
133
+ results.set(key, { result: availability.byItem[key] ?? null, policy: policies[index] ?? null });
134
+ });
135
+ return results;
136
+ }
137
+ async function loadStorefrontTranslations(em, ctx, requests) {
138
+ const pending = requests.filter((request) => request.ids.length > 0);
139
+ if (!pending.length) return /* @__PURE__ */ new Map();
140
+ try {
141
+ return await batchLoadTranslationsMany(
142
+ em.getKysely(),
143
+ pending.map((request) => ({ entityType: request.entityType, entityIds: request.ids })),
144
+ { tenantId: ctx.tenantId, organizationId: ctx.organizationId }
145
+ );
146
+ } catch (err) {
147
+ logger.error("[internal] ecommerce storefront translation overlay failed", {
148
+ tenantId: ctx.tenantId,
149
+ organizationId: ctx.organizationId,
150
+ storeId: ctx.store.id,
151
+ err
152
+ });
153
+ getTelemetryRuntime()?.reportError(err, {
154
+ module: "ecommerce",
155
+ code: "ecommerce.storefront_translations_failed",
156
+ attributes: { entityTypes: pending.length }
157
+ });
158
+ return /* @__PURE__ */ new Map();
159
+ }
160
+ }
161
+ function isImageMimeType(mimeType) {
162
+ return typeof mimeType === "string" && mimeType.trim().toLowerCase().startsWith("image/");
163
+ }
164
+ async function loadPublicPartitionCodes(em, ctx, codes) {
165
+ if (!codes.length) return /* @__PURE__ */ new Set();
166
+ const partitions = await findWithDecryption(
167
+ em,
168
+ AttachmentPartition,
169
+ {
170
+ code: { $in: codes },
171
+ isPublic: true,
172
+ $and: [
173
+ { $or: [{ tenantId: null }, { tenantId: ctx.tenantId }] },
174
+ { $or: [{ organizationId: null }, { organizationId: ctx.organizationId }] }
175
+ ]
176
+ },
177
+ { fields: ["id", "code"] },
178
+ { tenantId: ctx.tenantId, organizationId: ctx.organizationId }
179
+ );
180
+ return new Set(partitions.map((partition) => partition.code));
181
+ }
182
+ async function resolvePublicDefaultMediaUrls(em, ctx, refs) {
183
+ const urls = /* @__PURE__ */ new Map();
184
+ const pending = [];
185
+ for (const ref of refs) {
186
+ urls.set(ref.id, null);
187
+ const url = nonEmptyString(ref.defaultMediaUrl);
188
+ const mediaId = nonEmptyString(ref.defaultMediaId);
189
+ if (url && mediaId) pending.push({ productId: ref.id, mediaId, url });
190
+ }
191
+ if (!pending.length) return urls;
192
+ const mediaIds = Array.from(new Set(pending.map((entry) => entry.mediaId)));
193
+ const rows = await em.getKysely().selectFrom("attachments as a").innerJoin("attachment_partitions as p", "p.code", "a.partition_code").select(["a.id as id", "a.mime_type as mime_type"]).where(sql`${sql.ref("a.id")} = any(${mediaIds}::uuid[])`).where("a.tenant_id", "=", ctx.tenantId).where("a.organization_id", "=", ctx.organizationId).where("p.is_public", "=", true).where((eb) => eb.or([eb("p.tenant_id", "is", null), eb("p.tenant_id", "=", ctx.tenantId)])).where((eb) => eb.or([eb("p.organization_id", "is", null), eb("p.organization_id", "=", ctx.organizationId)])).execute();
194
+ const publicIds = new Set(rows.filter((row) => isImageMimeType(row.mime_type)).map((row) => row.id));
195
+ for (const entry of pending) if (publicIds.has(entry.mediaId)) urls.set(entry.productId, entry.url);
196
+ return urls;
197
+ }
198
+ function buildStorefrontListItem(source, parts) {
199
+ const { locales, overlay } = parts;
200
+ const price = parts.pricing?.price ?? null;
201
+ return {
202
+ id: source.id,
203
+ handle: nonEmptyString(source.handle),
204
+ title: localize(source.title, overlay, "title", locales) ?? "",
205
+ subtitle: localize(source.subtitle, overlay, "subtitle", locales),
206
+ defaultMediaUrl: nonEmptyString(source.defaultMediaUrl),
207
+ productType: nonEmptyString(source.productType) ?? "simple",
208
+ isConfigurable: parseBooleanFromUnknown(source.isConfigurable) === true,
209
+ hasVariants: parts.variantCount > 0,
210
+ variantCount: parts.variantCount,
211
+ categories: parts.categories.map((category) => ({
212
+ id: category.id,
213
+ name: localize(category.name, parts.categoryTranslations?.get(category.id), "name", locales) ?? category.name,
214
+ slug: category.slug
215
+ })),
216
+ tags: parts.tags.map(
217
+ (tag) => localize(tag.label, parts.tagTranslations?.get(tag.id), "label", locales) ?? tag.label
218
+ ),
219
+ price,
220
+ priceRange: parts.pricing?.priceRange ?? null,
221
+ availability: parts.availability,
222
+ badges: price?.isPromotion ? ["sale"] : []
223
+ };
224
+ }
225
+ export {
226
+ STOREFRONT_CATEGORY_ENTITY_TYPE,
227
+ STOREFRONT_OPTION_SCHEMA_ENTITY_TYPE,
228
+ STOREFRONT_PRODUCT_ENTITY_TYPE,
229
+ STOREFRONT_TAG_ENTITY_TYPE,
230
+ STOREFRONT_VARIANT_ENTITY_TYPE,
231
+ buildStorefrontListItem,
232
+ isCategoryInAssortment,
233
+ isImageMimeType,
234
+ loadPublicPartitionCodes,
235
+ loadStorefrontTranslations,
236
+ localeChain,
237
+ localize,
238
+ nonEmptyString,
239
+ referenceId,
240
+ resolvePublicDefaultMediaUrls,
241
+ resolveStorefrontAvailability,
242
+ selectCategoriesWithActiveAncestors,
243
+ storefrontAvailabilityKey,
244
+ stringList,
245
+ toStorefrontAvailability,
246
+ tryResolve
247
+ };
248
+ //# sourceMappingURL=storefrontCatalogSupport.js.map
@@ -0,0 +1,7 @@
1
+ {
2
+ "version": 3,
3
+ "sources": ["../../../../src/modules/ecommerce/lib/storefrontCatalogSupport.ts"],
4
+ "sourcesContent": ["import type { AwilixContainer } from 'awilix'\nimport type { EntityManager } from '@mikro-orm/postgresql'\nimport { sql } from 'kysely'\nimport {\n availabilityItemKey,\n resolveAvailability,\n type AvailabilityItemResult,\n type AvailabilityModuleConfigReader,\n type AvailabilityState,\n} from '@open-mercato/shared/lib/availability'\nimport { parseBooleanFromUnknown } from '@open-mercato/shared/lib/boolean'\nimport { findWithDecryption } from '@open-mercato/shared/lib/encryption/find'\nimport type { AssortmentScope, EffectiveAssortmentScope } from '@open-mercato/shared/lib/catalog-visibility'\nimport { createLogger } from '@open-mercato/shared/lib/logger'\nimport { getTelemetryRuntime } from '@open-mercato/shared/lib/telemetry/runtime'\nimport { AttachmentPartition } from '@open-mercato/core/modules/attachments/data/entities'\nimport { CatalogProductCategory } from '@open-mercato/core/modules/catalog/data/entities'\nimport { batchLoadTranslationsMany } from '@open-mercato/core/modules/translations/lib/batch'\nimport type { StorefrontPrice, StorefrontPriceRange, StorefrontProductPricing } from './storefrontPricing'\nimport type { StoreContext } from './types'\n\nconst logger = createLogger('ecommerce')\n\nexport const STOREFRONT_PRODUCT_ENTITY_TYPE = 'catalog:catalog_product'\nexport const STOREFRONT_VARIANT_ENTITY_TYPE = 'catalog:catalog_product_variant'\nexport const STOREFRONT_CATEGORY_ENTITY_TYPE = 'catalog:catalog_product_category'\nexport const STOREFRONT_TAG_ENTITY_TYPE = 'catalog:catalog_product_tag'\nexport const STOREFRONT_OPTION_SCHEMA_ENTITY_TYPE = 'catalog:catalog_option_schema_template'\n\nexport type StorefrontAvailability = {\n state: AvailabilityState\n canFulfil: boolean\n leadTimeDays: number | null\n releaseAt: string | null\n}\n\nexport type StorefrontProductListItem = {\n id: string\n handle: string | null\n title: string\n subtitle: string | null\n defaultMediaUrl: string | null\n productType: string\n isConfigurable: boolean\n hasVariants: boolean\n variantCount: number\n categories: Array<{ id: string; name: string; slug: string | null }>\n tags: string[]\n price: StorefrontPrice | null\n priceRange: StorefrontPriceRange | null\n availability: StorefrontAvailability\n badges: string[]\n}\n\nexport type CategoryLineage = { id: string; ancestorIds: string[]; descendantIds: string[] }\n\nexport type CategoryRef = { id: string; name: string; slug: string | null }\n\nexport type TagRef = { id: string; label: string }\n\nexport type TranslationOverlay = Record<string, Record<string, unknown>>\n\nexport type TranslationMap = Map<string, TranslationOverlay>\n\nexport type StorefrontPolicyResolutionScope = {\n tenantId: string\n organizationId: string\n storeId: string | null\n productId: string\n variantId: string | null\n}\n\ntype ResolvedPolicyField<T> = { value: T } | null | undefined\n\nexport type StorefrontResolvedPolicy = {\n hideWhenOutOfStock?: ResolvedPolicyField<boolean>\n minOrderQuantity?: ResolvedPolicyField<number | null>\n maxOrderQuantity?: ResolvedPolicyField<number | null>\n quantityIncrement?: ResolvedPolicyField<number | null>\n}\n\nexport type StorefrontPolicyResolver = {\n resolveMany(\n em: EntityManager,\n scopes: StorefrontPolicyResolutionScope[],\n ): Promise<Array<StorefrontResolvedPolicy | null | undefined>>\n}\n\nexport type StorefrontAvailabilityTarget = { productId: string; variantId: string | null }\n\nexport type ResolvedStorefrontAvailability = {\n result: AvailabilityItemResult | null\n policy: StorefrontResolvedPolicy | null\n}\n\nexport function tryResolve<T>(container: AwilixContainer, name: string): T | null {\n try {\n return (container.resolve(name) as T | null | undefined) ?? null\n } catch {\n return null\n }\n}\n\nexport function referenceId(value: unknown): string | null {\n if (typeof value === 'string') return value.length ? value : null\n if (value && typeof value === 'object' && 'id' in value) {\n const id = (value as { id: unknown }).id\n return typeof id === 'string' && id.length ? id : null\n }\n return null\n}\n\nexport function stringList(value: unknown): string[] {\n return Array.isArray(value) ? value.filter((entry): entry is string => typeof entry === 'string' && entry.length > 0) : []\n}\n\nexport function nonEmptyString(value: unknown): string | null {\n return typeof value === 'string' && value.trim().length > 0 ? value : null\n}\n\nfunction categoryBranchAdmits(branch: AssortmentScope, lineage: Set<string>, reach: Set<string>): boolean {\n const included = stringList(branch.categoryIds)\n if (included.length > 0 && !included.some((id) => reach.has(id))) return false\n if (stringList(branch.excludeCategoryIds).some((id) => lineage.has(id))) return false\n return (branch.allOf ?? []).every((nested) => categoryBranchAdmits(nested, lineage, reach))\n}\n\n/**\n * Whether a category can hold any product the effective assortment admits: one of its own subtree\n * or ancestors is granted by some branch, and neither it nor an ancestor is excluded there. Tag and\n * product conditions are product-level and do not decide a category's visibility.\n */\nexport function isCategoryInAssortment(category: CategoryLineage, scope: EffectiveAssortmentScope): boolean {\n if (scope === null) return true\n const lineage = new Set([category.id, ...category.ancestorIds])\n const reach = new Set([...lineage, ...category.descendantIds])\n return scope.some((branch) => categoryBranchAdmits(branch, lineage, reach))\n}\n\nexport type ActiveCategoryAncestor = { id: string; name: string; slug: string | null; ancestorIds: string[] }\n\n/**\n * Which of the given categories have every ancestor active and not deleted \u2014 the rule the category\n * tree applies, so a category hidden there under an inactive parent is hidden everywhere. Ancestors\n * listed in `known` (already checked active) need no lookup; the rest cost one categories query, and\n * none when every category is a root or has only known ancestors. `ancestors` holds the active\n * ancestor rows, fetched and known, for callers that render them (breadcrumbs).\n */\nexport async function selectCategoriesWithActiveAncestors(\n em: EntityManager,\n ctx: Pick<StoreContext, 'tenantId' | 'organizationId'>,\n categories: Array<{ id: string; ancestorIds: string[] }>,\n known: { activeIds?: Iterable<string>; ancestors?: Map<string, ActiveCategoryAncestor> } = {},\n): Promise<{ visibleIds: Set<string>; ancestors: Map<string, ActiveCategoryAncestor> }> {\n const ancestors = new Map(known.ancestors ?? [])\n const active = new Set([...(known.activeIds ?? []), ...ancestors.keys()])\n const unknown = Array.from(\n new Set(categories.flatMap((category) => category.ancestorIds.filter((id) => !active.has(id)))),\n )\n if (unknown.length > 0) {\n const rows = await findWithDecryption(\n em,\n CatalogProductCategory,\n { id: { $in: unknown }, tenantId: ctx.tenantId, organizationId: ctx.organizationId, deletedAt: null, isActive: true },\n { fields: ['id', 'name', 'slug', 'ancestorIds'] },\n { tenantId: ctx.tenantId, organizationId: ctx.organizationId },\n )\n for (const row of rows) {\n active.add(row.id)\n ancestors.set(row.id, { id: row.id, name: row.name, slug: row.slug ?? null, ancestorIds: stringList(row.ancestorIds) })\n }\n }\n const visibleIds = new Set(\n categories.filter((category) => category.ancestorIds.every((id) => active.has(id))).map((category) => category.id),\n )\n return { visibleIds, ancestors }\n}\n\nexport function localize(\n base: unknown,\n translations: TranslationOverlay | undefined,\n field: string,\n locales: string[],\n): string | null {\n for (const locale of locales) {\n const value = nonEmptyString(translations?.[locale]?.[field])\n if (value !== null) return value\n }\n return nonEmptyString(base)\n}\n\nexport function localeChain(ctx: StoreContext, preferredLocale?: string | null): string[] {\n const head = preferredLocale && ctx.store.supportedLocales.includes(preferredLocale) ? preferredLocale : ctx.effectiveLocale\n return Array.from(new Set([head, ctx.store.defaultLocale].filter((locale): locale is string => !!locale)))\n}\n\nexport function toStorefrontAvailability(result: AvailabilityItemResult | null): StorefrontAvailability {\n if (!result) return { state: 'not_tracked', canFulfil: true, leadTimeDays: null, releaseAt: null }\n return {\n state: result.state,\n canFulfil: result.canFulfil,\n leadTimeDays: result.leadTimeDays,\n releaseAt: result.releaseAt,\n }\n}\n\nexport function storefrontAvailabilityKey(target: StorefrontAvailabilityTarget): string {\n return availabilityItemKey({ catalogProductId: target.productId, catalogVariantId: target.variantId })\n}\n\n/**\n * One batched `resolveAvailability` (state) and one `policyResolutionService.resolveMany` (quantity\n * rules, `hideWhenOutOfStock`) for every product and variant of a response, keyed by\n * `storefrontAvailabilityKey`. A missing policy service yields `policy: null`.\n */\nexport async function resolveStorefrontAvailability(\n container: AwilixContainer,\n ctx: StoreContext,\n em: EntityManager,\n targets: StorefrontAvailabilityTarget[],\n): Promise<Map<string, ResolvedStorefrontAvailability>> {\n const results = new Map<string, ResolvedStorefrontAvailability>()\n if (!targets.length) return results\n const items = targets.map((target) => ({\n catalogProductId: target.productId,\n catalogVariantId: target.variantId,\n quantity: 1,\n }))\n const moduleConfig = tryResolve<AvailabilityModuleConfigReader>(container, 'moduleConfigService')\n const policyResolver = tryResolve<StorefrontPolicyResolver>(container, 'policyResolutionService')\n const [availability, policies] = await Promise.all([\n resolveAvailability(\n {\n tenantId: ctx.tenantId,\n organizationId: ctx.organizationId,\n storeId: ctx.store.id,\n channelId: ctx.channel?.salesChannelId ?? null,\n items,\n },\n moduleConfig ? { moduleConfig, container } : { container },\n ),\n policyResolver\n ? policyResolver.resolveMany(\n em,\n targets.map((target) => ({\n tenantId: ctx.tenantId,\n organizationId: ctx.organizationId,\n storeId: ctx.store.id,\n productId: target.productId,\n variantId: target.variantId,\n })),\n )\n : Promise.resolve([]),\n ])\n targets.forEach((target, index) => {\n const key = storefrontAvailabilityKey(target)\n results.set(key, { result: availability.byItem[key] ?? null, policy: policies[index] ?? null })\n })\n return results\n}\n\n/**\n * The response's translation overlays in one query (Storefront Public API \u00A77.2: one call per\n * response), keyed by entity type. A failure degrades to base fields and is reported.\n */\nexport async function loadStorefrontTranslations(\n em: EntityManager,\n ctx: StoreContext,\n requests: Array<{ entityType: string; ids: string[] }>,\n): Promise<Map<string, TranslationMap>> {\n const pending = requests.filter((request) => request.ids.length > 0)\n if (!pending.length) return new Map()\n try {\n return await batchLoadTranslationsMany(\n em.getKysely(),\n pending.map((request) => ({ entityType: request.entityType, entityIds: request.ids })),\n { tenantId: ctx.tenantId, organizationId: ctx.organizationId },\n )\n } catch (err) {\n logger.error('[internal] ecommerce storefront translation overlay failed', {\n tenantId: ctx.tenantId,\n organizationId: ctx.organizationId,\n storeId: ctx.store.id,\n err,\n })\n getTelemetryRuntime()?.reportError(err, {\n module: 'ecommerce',\n code: 'ecommerce.storefront_translations_failed',\n attributes: { entityTypes: pending.length },\n })\n return new Map()\n }\n}\n\nexport function isImageMimeType(mimeType: string | null | undefined): boolean {\n return typeof mimeType === 'string' && mimeType.trim().toLowerCase().startsWith('image/')\n}\n\n/** The codes among `codes` naming a public attachment partition visible to the store's tenant and organization. */\nexport async function loadPublicPartitionCodes(em: EntityManager, ctx: StoreContext, codes: string[]): Promise<Set<string>> {\n if (!codes.length) return new Set()\n const partitions = await findWithDecryption(\n em,\n AttachmentPartition,\n {\n code: { $in: codes },\n isPublic: true,\n $and: [\n { $or: [{ tenantId: null }, { tenantId: ctx.tenantId }] },\n { $or: [{ organizationId: null }, { organizationId: ctx.organizationId }] },\n ],\n },\n { fields: ['id', 'code'] },\n { tenantId: ctx.tenantId, organizationId: ctx.organizationId },\n )\n return new Set(partitions.map((partition) => partition.code))\n}\n\nexport type StorefrontDefaultMediaRef = { id: string; defaultMediaId?: unknown; defaultMediaUrl?: unknown }\n\ntype PublicMediaDatabase = {\n attachments: {\n id: string\n tenant_id: string | null\n organization_id: string | null\n partition_code: string\n mime_type: string\n }\n attachment_partitions: {\n code: string\n is_public: boolean\n tenant_id: string | null\n organization_id: string | null\n }\n}\n\n/**\n * Card `defaultMediaUrl`s held to the gallery rule of the product detail: a product's stored URL is\n * returned only while its `defaultMediaId` names an `image/*` attachment of the store's tenant and\n * organization in a public partition; otherwise it is `null`. One query for the whole page (the\n * attachments joined to their partitions, plain non-encrypted columns), none when no product\n * carries a default media id.\n */\nexport async function resolvePublicDefaultMediaUrls(\n em: EntityManager,\n ctx: StoreContext,\n refs: StorefrontDefaultMediaRef[],\n): Promise<Map<string, string | null>> {\n const urls = new Map<string, string | null>()\n const pending: Array<{ productId: string; mediaId: string; url: string }> = []\n for (const ref of refs) {\n urls.set(ref.id, null)\n const url = nonEmptyString(ref.defaultMediaUrl)\n const mediaId = nonEmptyString(ref.defaultMediaId)\n if (url && mediaId) pending.push({ productId: ref.id, mediaId, url })\n }\n if (!pending.length) return urls\n const mediaIds = Array.from(new Set(pending.map((entry) => entry.mediaId)))\n const rows = await em\n .getKysely<PublicMediaDatabase>()\n .selectFrom('attachments as a')\n .innerJoin('attachment_partitions as p', 'p.code', 'a.partition_code')\n .select(['a.id as id', 'a.mime_type as mime_type'])\n .where(sql<boolean>`${sql.ref('a.id')} = any(${mediaIds}::uuid[])`)\n .where('a.tenant_id', '=', ctx.tenantId)\n .where('a.organization_id', '=', ctx.organizationId)\n .where('p.is_public', '=', true)\n .where((eb) => eb.or([eb('p.tenant_id', 'is', null), eb('p.tenant_id', '=', ctx.tenantId)]))\n .where((eb) => eb.or([eb('p.organization_id', 'is', null), eb('p.organization_id', '=', ctx.organizationId)]))\n .execute()\n const publicIds = new Set(rows.filter((row) => isImageMimeType(row.mime_type)).map((row) => row.id))\n for (const entry of pending) if (publicIds.has(entry.mediaId)) urls.set(entry.productId, entry.url)\n return urls\n}\n\nexport type StorefrontListItemSource = {\n id: string\n handle?: unknown\n title?: unknown\n subtitle?: unknown\n defaultMediaUrl?: unknown\n productType?: unknown\n isConfigurable?: unknown\n}\n\nexport type StorefrontListItemParts = {\n locales: string[]\n overlay?: TranslationOverlay\n variantCount: number\n categories: CategoryRef[]\n categoryTranslations?: TranslationMap\n tags: TagRef[]\n tagTranslations?: TranslationMap\n pricing?: StorefrontProductPricing\n availability: StorefrontAvailability\n}\n\nexport function buildStorefrontListItem(\n source: StorefrontListItemSource,\n parts: StorefrontListItemParts,\n): StorefrontProductListItem {\n const { locales, overlay } = parts\n const price = parts.pricing?.price ?? null\n return {\n id: source.id,\n handle: nonEmptyString(source.handle),\n title: localize(source.title, overlay, 'title', locales) ?? '',\n subtitle: localize(source.subtitle, overlay, 'subtitle', locales),\n defaultMediaUrl: nonEmptyString(source.defaultMediaUrl),\n productType: nonEmptyString(source.productType) ?? 'simple',\n isConfigurable: parseBooleanFromUnknown(source.isConfigurable) === true,\n hasVariants: parts.variantCount > 0,\n variantCount: parts.variantCount,\n categories: parts.categories.map((category) => ({\n id: category.id,\n name: localize(category.name, parts.categoryTranslations?.get(category.id), 'name', locales) ?? category.name,\n slug: category.slug,\n })),\n tags: parts.tags.map(\n (tag) => localize(tag.label, parts.tagTranslations?.get(tag.id), 'label', locales) ?? tag.label,\n ),\n price,\n priceRange: parts.pricing?.priceRange ?? null,\n availability: parts.availability,\n badges: price?.isPromotion ? ['sale'] : [],\n }\n}\n"],
5
+ "mappings": "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",
6
+ "names": []
7
+ }
@@ -0,0 +1,241 @@
1
+ import { sql } from "kysely";
2
+ import { findWithDecryption } from "@open-mercato/shared/lib/encryption/find";
3
+ import { CatalogProductCategory } from "@open-mercato/core/modules/catalog/data/entities";
4
+ import { compareCodeUnits } from "@open-mercato/core/modules/catalog/lib/productScopeKeys";
5
+ import {
6
+ STOREFRONT_CATEGORY_ENTITY_TYPE,
7
+ isCategoryInAssortment,
8
+ loadStorefrontTranslations,
9
+ localeChain,
10
+ localize,
11
+ stringList,
12
+ tryResolve
13
+ } from "./storefrontCatalogSupport.js";
14
+ import { queryStorefrontProductUniverse } from "./storefrontFacets.js";
15
+ import { buildStorefrontProductScope } from "./storefrontProductScope.js";
16
+ const EMPTY_SNAPSHOT = { visible: /* @__PURE__ */ new Map(), counts: /* @__PURE__ */ new Map() };
17
+ function resolveRuntime(container, ctx) {
18
+ const em = tryResolve(container, "em");
19
+ const queryEngine = tryResolve(container, "queryEngine");
20
+ if (!em || !queryEngine) throw new Error("[internal] ecommerce storefront categories require em and queryEngine");
21
+ return {
22
+ ctx,
23
+ em,
24
+ queryEngine,
25
+ scope: buildStorefrontProductScope(ctx),
26
+ decryptionScope: { tenantId: ctx.tenantId, organizationId: ctx.organizationId }
27
+ };
28
+ }
29
+ function isDenyAll(ctx) {
30
+ const scope = ctx.buyer.assortmentScope;
31
+ return scope !== null && scope.length === 0;
32
+ }
33
+ async function loadCategoryRows(runtime) {
34
+ const { ctx } = runtime;
35
+ const rows = await findWithDecryption(
36
+ runtime.em,
37
+ CatalogProductCategory,
38
+ { tenantId: ctx.tenantId, organizationId: ctx.organizationId, deletedAt: null },
39
+ { fields: ["id", "name", "slug", "description", "depth", "parentId", "isActive", "ancestorIds", "descendantIds"] },
40
+ runtime.decryptionScope
41
+ );
42
+ return new Map(
43
+ rows.map((row) => [
44
+ row.id,
45
+ {
46
+ id: row.id,
47
+ name: row.name,
48
+ slug: row.slug ?? null,
49
+ description: row.description ?? null,
50
+ depth: typeof row.depth === "number" ? row.depth : 0,
51
+ parentId: row.parentId ?? null,
52
+ isActive: row.isActive !== false,
53
+ ancestorIds: stringList(row.ancestorIds),
54
+ descendantIds: stringList(row.descendantIds)
55
+ }
56
+ ])
57
+ );
58
+ }
59
+ function selectVisibleCategories(rows, ctx) {
60
+ const visible = /* @__PURE__ */ new Map();
61
+ for (const row of rows.values()) {
62
+ if (!row.isActive) continue;
63
+ if (!row.ancestorIds.every((id) => rows.get(id)?.isActive === true)) continue;
64
+ const lineage = { id: row.id, ancestorIds: row.ancestorIds, descendantIds: row.descendantIds };
65
+ if (!isCategoryInAssortment(lineage, ctx.buyer.assortmentScope)) continue;
66
+ visible.set(row.id, row);
67
+ }
68
+ return visible;
69
+ }
70
+ async function loadStorefrontVisibleCategories(runtime) {
71
+ return selectVisibleCategories(await loadCategoryRows(runtime), runtime.ctx);
72
+ }
73
+ async function loadAssignedCategoryIds(runtime, productIds) {
74
+ const { tenantId, organizationId } = runtime.decryptionScope;
75
+ const rows = await runtime.em.getKysely().selectFrom("catalog_product_category_assignments as ca").select(["ca.product_id as product_id", "ca.category_id as category_id"]).where(sql`${sql.ref("ca.product_id")} = any(${productIds}::uuid[])`).where("ca.tenant_id", "=", tenantId).where("ca.organization_id", "=", organizationId).execute();
76
+ const byProduct = /* @__PURE__ */ new Map();
77
+ for (const row of rows) {
78
+ const bucket = byProduct.get(row.product_id) ?? [];
79
+ bucket.push(row.category_id);
80
+ byProduct.set(row.product_id, bucket);
81
+ }
82
+ return byProduct;
83
+ }
84
+ function countProductsPerCategory(assignedByProduct, ancestorsOf) {
85
+ const counts = /* @__PURE__ */ new Map();
86
+ for (const assigned of assignedByProduct.values()) {
87
+ const reached = /* @__PURE__ */ new Set();
88
+ for (const categoryId of assigned) {
89
+ const ancestors = ancestorsOf(categoryId);
90
+ if (!ancestors) continue;
91
+ reached.add(categoryId);
92
+ for (const ancestorId of ancestors) reached.add(ancestorId);
93
+ }
94
+ for (const categoryId of reached) counts.set(categoryId, (counts.get(categoryId) ?? 0) + 1);
95
+ }
96
+ return counts;
97
+ }
98
+ async function loadCategoryCounts(runtime, rows, universe) {
99
+ if (universe === null) return null;
100
+ const productIds = Array.from(
101
+ new Set(universe.flatMap((record) => typeof record.id === "string" ? [record.id] : []))
102
+ );
103
+ if (productIds.length === 0) return /* @__PURE__ */ new Map();
104
+ const assigned = await loadAssignedCategoryIds(runtime, productIds);
105
+ return countProductsPerCategory(assigned, (categoryId) => rows.get(categoryId)?.ancestorIds ?? null);
106
+ }
107
+ async function loadCategorySnapshot(runtime, targetVisible) {
108
+ if (isDenyAll(runtime.ctx)) return EMPTY_SNAPSHOT;
109
+ if (targetVisible) {
110
+ const rows2 = await loadCategoryRows(runtime);
111
+ const visible2 = selectVisibleCategories(rows2, runtime.ctx);
112
+ if (visible2.size === 0 || !targetVisible(visible2)) return { visible: visible2, counts: /* @__PURE__ */ new Map() };
113
+ const universe2 = await queryStorefrontProductUniverse(runtime, runtime.scope.filters);
114
+ return { visible: visible2, counts: await loadCategoryCounts(runtime, rows2, universe2) };
115
+ }
116
+ const [rows, universe] = await Promise.all([
117
+ loadCategoryRows(runtime),
118
+ queryStorefrontProductUniverse(runtime, runtime.scope.filters)
119
+ ]);
120
+ const visible = selectVisibleCategories(rows, runtime.ctx);
121
+ if (visible.size === 0) return { visible, counts: /* @__PURE__ */ new Map() };
122
+ return { visible, counts: await loadCategoryCounts(runtime, rows, universe) };
123
+ }
124
+ function productCountOf(counts, id) {
125
+ return counts === null ? null : counts.get(id) ?? 0;
126
+ }
127
+ function isNonEmpty(counts, id) {
128
+ return counts === null || (counts.get(id) ?? 0) > 0;
129
+ }
130
+ function compareNodes(left, right) {
131
+ return left.name.localeCompare(right.name) || compareCodeUnits(left.id, right.id);
132
+ }
133
+ function localizedName(row, translations, locales) {
134
+ return localize(row.name, translations?.get(row.id), "name", locales) ?? row.name;
135
+ }
136
+ function localizedDescription(row, translations, locales) {
137
+ return localize(row.description, translations?.get(row.id), "description", locales);
138
+ }
139
+ function childrenIndex(visible, eligible) {
140
+ const index = /* @__PURE__ */ new Map();
141
+ for (const row of visible.values()) {
142
+ if (!eligible(row)) continue;
143
+ const parentKey = row.parentId !== null && visible.has(row.parentId) ? row.parentId : null;
144
+ if (row.parentId !== null && parentKey === null) continue;
145
+ const bucket = index.get(parentKey) ?? [];
146
+ bucket.push(row);
147
+ index.set(parentKey, bucket);
148
+ }
149
+ return index;
150
+ }
151
+ function collectTreeIds(index, parentKey, levels, into) {
152
+ if (levels <= 0) return;
153
+ for (const row of index.get(parentKey) ?? []) {
154
+ into.push(row.id);
155
+ collectTreeIds(index, row.id, levels - 1, into);
156
+ }
157
+ }
158
+ async function getStorefrontCategoryTree(container, ctx, query) {
159
+ const runtime = resolveRuntime(container, ctx);
160
+ const parentKey = query.parentId ?? null;
161
+ const { visible, counts } = await loadCategorySnapshot(
162
+ runtime,
163
+ parentKey === null ? void 0 : (candidates) => candidates.has(parentKey)
164
+ );
165
+ if (parentKey !== null && !visible.has(parentKey)) return { tree: [], effectiveLocale: ctx.effectiveLocale };
166
+ const includeEmpty = query.includeEmpty === true;
167
+ const index = childrenIndex(visible, (row) => includeEmpty || isNonEmpty(counts, row.id));
168
+ const levels = query.depth ?? Number.POSITIVE_INFINITY;
169
+ const ids = [];
170
+ collectTreeIds(index, parentKey, levels, ids);
171
+ const translations = (await loadStorefrontTranslations(runtime.em, ctx, [{ entityType: STOREFRONT_CATEGORY_ENTITY_TYPE, ids }])).get(STOREFRONT_CATEGORY_ENTITY_TYPE);
172
+ const locales = localeChain(ctx);
173
+ const build = (parent, remaining) => {
174
+ if (remaining <= 0) return [];
175
+ return (index.get(parent) ?? []).map((row) => ({ row, name: localizedName(row, translations, locales) })).sort((left, right) => compareNodes({ name: left.name, id: left.row.id }, { name: right.name, id: right.row.id })).map(({ row, name }) => ({
176
+ id: row.id,
177
+ name,
178
+ slug: row.slug,
179
+ description: localizedDescription(row, translations, locales),
180
+ depth: row.depth,
181
+ parentId: row.parentId,
182
+ productCount: productCountOf(counts, row.id),
183
+ hasChildren: (index.get(row.id)?.length ?? 0) > 0,
184
+ children: build(row.id, remaining - 1)
185
+ }));
186
+ };
187
+ return { tree: build(parentKey, levels), effectiveLocale: ctx.effectiveLocale };
188
+ }
189
+ async function getStorefrontCategoryLanding(container, ctx, slug) {
190
+ const runtime = resolveRuntime(container, ctx);
191
+ const findBySlug = (candidates) => Array.from(candidates.values()).find((candidate) => candidate.slug === slug);
192
+ const { visible, counts } = await loadCategorySnapshot(runtime, (candidates) => findBySlug(candidates) !== void 0);
193
+ const row = findBySlug(visible);
194
+ if (!row) return null;
195
+ const ancestors = row.ancestorIds.flatMap((id) => {
196
+ const ancestor = visible.get(id);
197
+ return ancestor ? [ancestor] : [];
198
+ });
199
+ const children = Array.from(visible.values()).filter(
200
+ (candidate) => candidate.parentId === row.id && isNonEmpty(counts, candidate.id)
201
+ );
202
+ const translations = (await loadStorefrontTranslations(runtime.em, ctx, [
203
+ {
204
+ entityType: STOREFRONT_CATEGORY_ENTITY_TYPE,
205
+ ids: [row.id, ...ancestors.map((ancestor) => ancestor.id), ...children.map((child) => child.id)]
206
+ }
207
+ ])).get(STOREFRONT_CATEGORY_ENTITY_TYPE);
208
+ const locales = localeChain(ctx);
209
+ return {
210
+ category: {
211
+ id: row.id,
212
+ name: localizedName(row, translations, locales),
213
+ slug: row.slug,
214
+ description: localizedDescription(row, translations, locales),
215
+ depth: row.depth,
216
+ parentId: row.parentId,
217
+ ancestorIds: ancestors.map((ancestor) => ancestor.id),
218
+ breadcrumb: [...ancestors, row].map((entry) => ({
219
+ id: entry.id,
220
+ name: localizedName(entry, translations, locales),
221
+ slug: entry.slug
222
+ })),
223
+ children: children.map((child) => ({
224
+ id: child.id,
225
+ name: localizedName(child, translations, locales),
226
+ slug: child.slug,
227
+ productCount: productCountOf(counts, child.id)
228
+ })).sort(compareNodes),
229
+ productCount: productCountOf(counts, row.id),
230
+ seo: { title: null, description: null, canonicalUrl: null }
231
+ },
232
+ effectiveLocale: ctx.effectiveLocale
233
+ };
234
+ }
235
+ export {
236
+ countProductsPerCategory,
237
+ getStorefrontCategoryLanding,
238
+ getStorefrontCategoryTree,
239
+ loadStorefrontVisibleCategories
240
+ };
241
+ //# sourceMappingURL=storefrontCategories.js.map
@@ -0,0 +1,7 @@
1
+ {
2
+ "version": 3,
3
+ "sources": ["../../../../src/modules/ecommerce/lib/storefrontCategories.ts"],
4
+ "sourcesContent": ["import type { AwilixContainer } from 'awilix'\nimport type { EntityManager } from '@mikro-orm/postgresql'\nimport { sql } from 'kysely'\nimport { findWithDecryption } from '@open-mercato/shared/lib/encryption/find'\nimport type { QueryEngine } from '@open-mercato/shared/lib/query/types'\nimport { CatalogProductCategory } from '@open-mercato/core/modules/catalog/data/entities'\nimport { compareCodeUnits } from '@open-mercato/core/modules/catalog/lib/productScopeKeys'\nimport type { EcommerceStorefrontCategoryTreeQuery } from '../data/validators'\nimport {\n STOREFRONT_CATEGORY_ENTITY_TYPE,\n isCategoryInAssortment,\n loadStorefrontTranslations,\n localeChain,\n localize,\n stringList,\n tryResolve,\n type TranslationMap,\n} from './storefrontCatalogSupport'\nimport { queryStorefrontProductUniverse, type StorefrontFacetRuntime } from './storefrontFacets'\nimport { buildStorefrontProductScope } from './storefrontProductScope'\nimport type { StoreContext } from './types'\n\n/**\n * Category tree and category landing (Storefront Public API rev 4 \u00A74.3, \u00A74.4).\n *\n * A category is visible to a buyer when it is active, every ancestor is active (so the tree can\n * reach it) and `isCategoryInAssortment` admits it. Counts are descendant-inclusive and taken over\n * the buyer's assortment universe (`buildStorefrontProductScope`: tenant, organization, active,\n * not deleted, inside the effective scope), so a restricted buyer never sees a count for products\n * they cannot browse. Nothing here depends on prices, so the results are keyed on\n * `assortmentScopeHash` by `storefrontCategoryCache.ts`.\n *\n * Query shape, uncached: categories, the scoped product ids, the category assignments of those\n * products, and one multi-entity translation query \u2014 four, regardless of tree size. A buyer whose\n * assortment is deny-all (`[]`, e.g. a closed `require_authentication` channel) costs none.\n *\n * The product universe is capped at `STOREFRONT_FACET_UNIVERSE_CAP`: an assortment larger than\n * that is not counted, every `productCount` is `null` and no category is dropped as empty.\n */\n\nexport type StorefrontCategoryNode = {\n id: string\n name: string\n slug: string | null\n description: string | null\n depth: number\n parentId: string | null\n productCount: number | null\n hasChildren: boolean\n children: StorefrontCategoryNode[]\n}\n\nexport type StorefrontCategoryTreeResponse = {\n tree: StorefrontCategoryNode[]\n effectiveLocale: string\n}\n\nexport type StorefrontCategoryBreadcrumbEntry = { id: string; name: string; slug: string | null }\n\nexport type StorefrontCategoryChild = { id: string; name: string; slug: string | null; productCount: number | null }\n\nexport type StorefrontCategorySeo = { title: string | null; description: string | null; canonicalUrl: string | null }\n\nexport type StorefrontCategoryLanding = {\n id: string\n name: string\n slug: string | null\n description: string | null\n depth: number\n parentId: string | null\n ancestorIds: string[]\n breadcrumb: StorefrontCategoryBreadcrumbEntry[]\n children: StorefrontCategoryChild[]\n productCount: number | null\n seo: StorefrontCategorySeo\n}\n\nexport type StorefrontCategoryLandingBlock = {\n category: StorefrontCategoryLanding\n effectiveLocale: string\n}\n\ntype CategoryRow = {\n id: string\n name: string\n slug: string | null\n description: string | null\n depth: number\n parentId: string | null\n isActive: boolean\n ancestorIds: string[]\n descendantIds: string[]\n}\n\ntype CategorySnapshot = {\n visible: Map<string, CategoryRow>\n /** `null` when the assortment exceeds the universe cap and was not counted. */\n counts: Map<string, number> | null\n}\n\ntype CategoryAssignmentDatabase = {\n catalog_product_category_assignments: {\n product_id: string\n category_id: string\n tenant_id: string\n organization_id: string\n }\n}\n\nconst EMPTY_SNAPSHOT: CategorySnapshot = { visible: new Map(), counts: new Map() }\n\nfunction resolveRuntime(container: AwilixContainer, ctx: StoreContext): StorefrontFacetRuntime {\n const em = tryResolve<EntityManager>(container, 'em')\n const queryEngine = tryResolve<QueryEngine>(container, 'queryEngine')\n if (!em || !queryEngine) throw new Error('[internal] ecommerce storefront categories require em and queryEngine')\n return {\n ctx,\n em,\n queryEngine,\n scope: buildStorefrontProductScope(ctx),\n decryptionScope: { tenantId: ctx.tenantId, organizationId: ctx.organizationId },\n }\n}\n\nfunction isDenyAll(ctx: StoreContext): boolean {\n const scope = ctx.buyer.assortmentScope\n return scope !== null && scope.length === 0\n}\n\ntype CategoryLoadRuntime = Pick<StorefrontFacetRuntime, 'ctx' | 'em' | 'decryptionScope'>\n\nexport type StorefrontVisibleCategory = CategoryRow\n\nasync function loadCategoryRows(runtime: CategoryLoadRuntime): Promise<Map<string, CategoryRow>> {\n const { ctx } = runtime\n const rows = await findWithDecryption(\n runtime.em,\n CatalogProductCategory,\n { tenantId: ctx.tenantId, organizationId: ctx.organizationId, deletedAt: null },\n { fields: ['id', 'name', 'slug', 'description', 'depth', 'parentId', 'isActive', 'ancestorIds', 'descendantIds'] },\n runtime.decryptionScope,\n )\n return new Map(\n rows.map((row) => [\n row.id,\n {\n id: row.id,\n name: row.name,\n slug: row.slug ?? null,\n description: row.description ?? null,\n depth: typeof row.depth === 'number' ? row.depth : 0,\n parentId: row.parentId ?? null,\n isActive: row.isActive !== false,\n ancestorIds: stringList(row.ancestorIds),\n descendantIds: stringList(row.descendantIds),\n },\n ]),\n )\n}\n\nfunction selectVisibleCategories(rows: Map<string, CategoryRow>, ctx: StoreContext): Map<string, CategoryRow> {\n const visible = new Map<string, CategoryRow>()\n for (const row of rows.values()) {\n if (!row.isActive) continue\n if (!row.ancestorIds.every((id) => rows.get(id)?.isActive === true)) continue\n const lineage = { id: row.id, ancestorIds: row.ancestorIds, descendantIds: row.descendantIds }\n if (!isCategoryInAssortment(lineage, ctx.buyer.assortmentScope)) continue\n visible.set(row.id, row)\n }\n return visible\n}\n\n/** The tenant's categories visible to the buyer (see the module header): one categories query. */\nexport async function loadStorefrontVisibleCategories(runtime: CategoryLoadRuntime): Promise<Map<string, StorefrontVisibleCategory>> {\n return selectVisibleCategories(await loadCategoryRows(runtime), runtime.ctx)\n}\n\nasync function loadAssignedCategoryIds(\n runtime: StorefrontFacetRuntime,\n productIds: string[],\n): Promise<Map<string, string[]>> {\n const { tenantId, organizationId } = runtime.decryptionScope\n const rows = await runtime.em\n .getKysely<CategoryAssignmentDatabase>()\n .selectFrom('catalog_product_category_assignments as ca')\n .select(['ca.product_id as product_id', 'ca.category_id as category_id'])\n .where(sql<boolean>`${sql.ref('ca.product_id')} = any(${productIds}::uuid[])`)\n .where('ca.tenant_id', '=', tenantId)\n .where('ca.organization_id', '=', organizationId)\n .execute()\n const byProduct = new Map<string, string[]>()\n for (const row of rows) {\n const bucket = byProduct.get(row.product_id) ?? []\n bucket.push(row.category_id)\n byProduct.set(row.product_id, bucket)\n }\n return byProduct\n}\n\n/**\n * Descendant-inclusive product counts: a product counts once for every category it is assigned to\n * and once for each ancestor of those, however many of its assignments share an ancestor.\n */\nexport function countProductsPerCategory(\n assignedByProduct: Map<string, string[]>,\n ancestorsOf: (categoryId: string) => string[] | null,\n): Map<string, number> {\n const counts = new Map<string, number>()\n for (const assigned of assignedByProduct.values()) {\n const reached = new Set<string>()\n for (const categoryId of assigned) {\n const ancestors = ancestorsOf(categoryId)\n if (!ancestors) continue\n reached.add(categoryId)\n for (const ancestorId of ancestors) reached.add(ancestorId)\n }\n for (const categoryId of reached) counts.set(categoryId, (counts.get(categoryId) ?? 0) + 1)\n }\n return counts\n}\n\nasync function loadCategoryCounts(\n runtime: StorefrontFacetRuntime,\n rows: Map<string, CategoryRow>,\n universe: Array<{ id?: unknown }> | null,\n): Promise<Map<string, number> | null> {\n if (universe === null) return null\n const productIds = Array.from(\n new Set(universe.flatMap((record) => (typeof record.id === 'string' ? [record.id] : []))),\n )\n if (productIds.length === 0) return new Map()\n const assigned = await loadAssignedCategoryIds(runtime, productIds)\n return countProductsPerCategory(assigned, (categoryId) => rows.get(categoryId)?.ancestorIds ?? null)\n}\n\n/**\n * With `targetVisible`, the categories query runs first and the product universe is loaded only\n * when the requested category is visible, so an unknown slug or `parentId` costs one categories\n * query instead of three catalogue-sized ones.\n */\nasync function loadCategorySnapshot(\n runtime: StorefrontFacetRuntime,\n targetVisible?: (visible: Map<string, CategoryRow>) => boolean,\n): Promise<CategorySnapshot> {\n if (isDenyAll(runtime.ctx)) return EMPTY_SNAPSHOT\n if (targetVisible) {\n const rows = await loadCategoryRows(runtime)\n const visible = selectVisibleCategories(rows, runtime.ctx)\n if (visible.size === 0 || !targetVisible(visible)) return { visible, counts: new Map() }\n const universe = await queryStorefrontProductUniverse(runtime, runtime.scope.filters)\n return { visible, counts: await loadCategoryCounts(runtime, rows, universe) }\n }\n const [rows, universe] = await Promise.all([\n loadCategoryRows(runtime),\n queryStorefrontProductUniverse(runtime, runtime.scope.filters),\n ])\n const visible = selectVisibleCategories(rows, runtime.ctx)\n if (visible.size === 0) return { visible, counts: new Map() }\n return { visible, counts: await loadCategoryCounts(runtime, rows, universe) }\n}\n\nfunction productCountOf(counts: Map<string, number> | null, id: string): number | null {\n return counts === null ? null : counts.get(id) ?? 0\n}\n\nfunction isNonEmpty(counts: Map<string, number> | null, id: string): boolean {\n return counts === null || (counts.get(id) ?? 0) > 0\n}\n\nfunction compareNodes(left: { name: string; id: string }, right: { name: string; id: string }): number {\n return left.name.localeCompare(right.name) || compareCodeUnits(left.id, right.id)\n}\n\nfunction localizedName(row: CategoryRow, translations: TranslationMap | undefined, locales: string[]): string {\n return localize(row.name, translations?.get(row.id), 'name', locales) ?? row.name\n}\n\nfunction localizedDescription(row: CategoryRow, translations: TranslationMap | undefined, locales: string[]): string | null {\n return localize(row.description, translations?.get(row.id), 'description', locales)\n}\n\nfunction childrenIndex(\n visible: Map<string, CategoryRow>,\n eligible: (row: CategoryRow) => boolean,\n): Map<string | null, CategoryRow[]> {\n const index = new Map<string | null, CategoryRow[]>()\n for (const row of visible.values()) {\n if (!eligible(row)) continue\n const parentKey = row.parentId !== null && visible.has(row.parentId) ? row.parentId : null\n if (row.parentId !== null && parentKey === null) continue\n const bucket = index.get(parentKey) ?? []\n bucket.push(row)\n index.set(parentKey, bucket)\n }\n return index\n}\n\nfunction collectTreeIds(\n index: Map<string | null, CategoryRow[]>,\n parentKey: string | null,\n levels: number,\n into: string[],\n): void {\n if (levels <= 0) return\n for (const row of index.get(parentKey) ?? []) {\n into.push(row.id)\n collectTreeIds(index, row.id, levels - 1, into)\n }\n}\n\n/**\n * `GET /categories`: the visible tree below `parentId` (the roots when omitted), `depth` levels\n * deep (all levels when omitted). Empty categories are dropped unless `includeEmpty`; a node\n * beyond `depth` is not expanded but still reports `hasChildren`. An unknown, hidden or\n * out-of-assortment `parentId` yields an empty tree, exactly like a nonexistent one.\n */\nexport async function getStorefrontCategoryTree(\n container: AwilixContainer,\n ctx: StoreContext,\n query: Pick<EcommerceStorefrontCategoryTreeQuery, 'parentId' | 'depth' | 'includeEmpty'>,\n): Promise<StorefrontCategoryTreeResponse> {\n const runtime = resolveRuntime(container, ctx)\n const parentKey = query.parentId ?? null\n const { visible, counts } = await loadCategorySnapshot(\n runtime,\n parentKey === null ? undefined : (candidates) => candidates.has(parentKey),\n )\n if (parentKey !== null && !visible.has(parentKey)) return { tree: [], effectiveLocale: ctx.effectiveLocale }\n const includeEmpty = query.includeEmpty === true\n const index = childrenIndex(visible, (row) => includeEmpty || isNonEmpty(counts, row.id))\n const levels = query.depth ?? Number.POSITIVE_INFINITY\n const ids: string[] = []\n collectTreeIds(index, parentKey, levels, ids)\n const translations = (\n await loadStorefrontTranslations(runtime.em, ctx, [{ entityType: STOREFRONT_CATEGORY_ENTITY_TYPE, ids }])\n ).get(STOREFRONT_CATEGORY_ENTITY_TYPE)\n const locales = localeChain(ctx)\n\n const build = (parent: string | null, remaining: number): StorefrontCategoryNode[] => {\n if (remaining <= 0) return []\n return (index.get(parent) ?? [])\n .map((row) => ({ row, name: localizedName(row, translations, locales) }))\n .sort((left, right) => compareNodes({ name: left.name, id: left.row.id }, { name: right.name, id: right.row.id }))\n .map(({ row, name }) => ({\n id: row.id,\n name,\n slug: row.slug,\n description: localizedDescription(row, translations, locales),\n depth: row.depth,\n parentId: row.parentId,\n productCount: productCountOf(counts, row.id),\n hasChildren: (index.get(row.id)?.length ?? 0) > 0,\n children: build(row.id, remaining - 1),\n }))\n }\n return { tree: build(parentKey, levels), effectiveLocale: ctx.effectiveLocale }\n}\n\n/**\n * The category block of `GET /categories/:slug`. `null` for a slug that matches no visible\n * category \u2014 nonexistent, inactive, deleted, another tenant's, outside the buyer's assortment, or\n * under an inactive ancestor \u2014 all through the same single categories query, so the three cases\n * cannot be told apart by cost or by answer.\n */\nexport async function getStorefrontCategoryLanding(\n container: AwilixContainer,\n ctx: StoreContext,\n slug: string,\n): Promise<StorefrontCategoryLandingBlock | null> {\n const runtime = resolveRuntime(container, ctx)\n const findBySlug = (candidates: Map<string, CategoryRow>) =>\n Array.from(candidates.values()).find((candidate) => candidate.slug === slug)\n const { visible, counts } = await loadCategorySnapshot(runtime, (candidates) => findBySlug(candidates) !== undefined)\n const row = findBySlug(visible)\n if (!row) return null\n const ancestors = row.ancestorIds.flatMap((id) => {\n const ancestor = visible.get(id)\n return ancestor ? [ancestor] : []\n })\n const children = Array.from(visible.values()).filter(\n (candidate) => candidate.parentId === row.id && isNonEmpty(counts, candidate.id),\n )\n const translations = (\n await loadStorefrontTranslations(runtime.em, ctx, [\n {\n entityType: STOREFRONT_CATEGORY_ENTITY_TYPE,\n ids: [row.id, ...ancestors.map((ancestor) => ancestor.id), ...children.map((child) => child.id)],\n },\n ])\n ).get(STOREFRONT_CATEGORY_ENTITY_TYPE)\n const locales = localeChain(ctx)\n return {\n category: {\n id: row.id,\n name: localizedName(row, translations, locales),\n slug: row.slug,\n description: localizedDescription(row, translations, locales),\n depth: row.depth,\n parentId: row.parentId,\n ancestorIds: ancestors.map((ancestor) => ancestor.id),\n breadcrumb: [...ancestors, row].map((entry) => ({\n id: entry.id,\n name: localizedName(entry, translations, locales),\n slug: entry.slug,\n })),\n children: children\n .map((child) => ({\n id: child.id,\n name: localizedName(child, translations, locales),\n slug: child.slug,\n productCount: productCountOf(counts, child.id),\n }))\n .sort(compareNodes),\n productCount: productCountOf(counts, row.id),\n seo: { title: null, description: null, canonicalUrl: null },\n },\n effectiveLocale: ctx.effectiveLocale,\n }\n}\n"],
5
+ "mappings": "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",
6
+ "names": ["rows", "visible", "universe"]
7
+ }