@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,71 @@
1
+ import type { EntityManager } from '@mikro-orm/postgresql'
2
+ import { runWithCacheTenant } from '@open-mercato/cache'
3
+ import { composeStoreContext, resolveBuyerContext } from './buyerContext'
4
+ import { buyerContextCache, ecommerceResolutionCache, ecommerceStoreTag, type CacheContainer } from './cacheKeys'
5
+ import {
6
+ resolveStoreBySlug,
7
+ resolveStoreFromRequest,
8
+ type ResolveStoreBySlugOptions,
9
+ type ResolveStoreFromRequestOptions,
10
+ } from './storeContext'
11
+ import type { StoreContext } from './types'
12
+
13
+ /**
14
+ * `storeContextService` (SPEC-029 §6): composes the store layer (`storeContext.ts`) and the
15
+ * buyer layer (`buyerContext.ts`) into a full `StoreContext`. Registered per request container.
16
+ */
17
+
18
+ export type ResolveStoreContextBySlugOptions = ResolveStoreBySlugOptions & {
19
+ request?: Request | null
20
+ }
21
+
22
+ export type InvalidateStoreContextOptions = { tenantId?: string | null }
23
+
24
+ export type StoreContextService = {
25
+ resolve(request: Request, opts?: ResolveStoreFromRequestOptions): Promise<StoreContext>
26
+ resolveBySlug(slug: string, opts?: ResolveStoreContextBySlugOptions): Promise<StoreContext>
27
+ invalidate(storeId: string, opts?: InvalidateStoreContextOptions): Promise<void>
28
+ }
29
+
30
+ type StoreTenantDatabase = {
31
+ ecommerce_stores: { id: string; tenant_id: string }
32
+ }
33
+
34
+ async function lookupStoreTenantId(container: CacheContainer, storeId: string): Promise<string | null> {
35
+ let em: EntityManager | null = null
36
+ try {
37
+ em = (container.resolve('em') as EntityManager | null | undefined) ?? null
38
+ } catch {
39
+ em = null
40
+ }
41
+ if (!em) return null
42
+ const row = await em
43
+ .getKysely<StoreTenantDatabase>()
44
+ .selectFrom('ecommerce_stores')
45
+ .select('tenant_id')
46
+ .where('id', '=', storeId)
47
+ .executeTakeFirst()
48
+ return row?.tenant_id ?? null
49
+ }
50
+
51
+ export function createStoreContextService(container: CacheContainer): StoreContextService {
52
+ return {
53
+ async resolve(request, opts) {
54
+ const store = await resolveStoreFromRequest(container, request, opts)
55
+ const buyer = await runWithCacheTenant(store.tenantId, () => resolveBuyerContext(container, store, request))
56
+ return composeStoreContext(store, buyer)
57
+ },
58
+ async resolveBySlug(slug, opts = {}) {
59
+ const { request = null, ...slugOptions } = opts
60
+ const store = await resolveStoreBySlug(container, slug, slugOptions)
61
+ const buyer = await runWithCacheTenant(store.tenantId, () => resolveBuyerContext(container, store, request))
62
+ return composeStoreContext(store, buyer)
63
+ },
64
+ async invalidate(storeId, opts = {}) {
65
+ const tags = [ecommerceStoreTag(storeId)]
66
+ await ecommerceResolutionCache(container).deleteByTags(tags)
67
+ const tenantId = opts.tenantId ?? (await lookupStoreTenantId(container, storeId))
68
+ if (tenantId) await buyerContextCache(container, { tenantId, storeId }).deleteByTags(tags)
69
+ },
70
+ }
71
+ }
@@ -0,0 +1,88 @@
1
+ /**
2
+ * Storefront locale negotiation (SPEC-029 §4.1 step 7, §6.2): `?locale` → `X-Locale` →
3
+ * `Accept-Language` (q-values honored) → `store.defaultLocale`. A candidate is used only when it
4
+ * matches one of `store.supportedLocales`; an unsupported request falls back without failing.
5
+ */
6
+
7
+ export type StoreLocaleSettings = {
8
+ defaultLocale: string
9
+ supportedLocales: string[]
10
+ }
11
+
12
+ export type StoreLocaleCandidates = {
13
+ queryLocale?: string | null
14
+ headerLocale?: string | null
15
+ acceptLanguage?: string | null
16
+ }
17
+
18
+ export type StoreLocaleResolution = {
19
+ effectiveLocale: string
20
+ requestedLocale: string | null
21
+ }
22
+
23
+ const LOCALE_TAG_PATTERN = /^[A-Za-z]{2,8}(?:-[A-Za-z0-9]{1,8})*$/
24
+ const LOCALE_TAG_MAX_LENGTH = 35
25
+ const ACCEPT_LANGUAGE_MAX_LENGTH = 1024
26
+ const ACCEPT_LANGUAGE_MAX_ENTRIES = 20
27
+ const QUALITY_PATTERN = /^q=([01](?:\.\d{0,3})?)$/i
28
+
29
+ export function sanitizeLocaleTag(value: string | null | undefined): string | null {
30
+ if (typeof value !== 'string') return null
31
+ const trimmed = value.trim()
32
+ if (!trimmed.length || trimmed.length > LOCALE_TAG_MAX_LENGTH) return null
33
+ const normalized = trimmed.replace(/_/g, '-')
34
+ return LOCALE_TAG_PATTERN.test(normalized) ? normalized : null
35
+ }
36
+
37
+ export function parseAcceptLanguage(header: string | null | undefined): string[] {
38
+ if (typeof header !== 'string' || !header.trim().length) return []
39
+ const entries = header
40
+ .slice(0, ACCEPT_LANGUAGE_MAX_LENGTH)
41
+ .split(',')
42
+ .slice(0, ACCEPT_LANGUAGE_MAX_ENTRIES)
43
+ .map((part, index) => {
44
+ const [rawTag, ...params] = part.split(';')
45
+ let quality = 1
46
+ for (const param of params) {
47
+ const match = QUALITY_PATTERN.exec(param.trim())
48
+ if (match) quality = Number.parseFloat(match[1])
49
+ }
50
+ return { tag: sanitizeLocaleTag(rawTag), quality, index }
51
+ })
52
+ .filter((entry): entry is { tag: string; quality: number; index: number } => entry.tag !== null && entry.quality > 0)
53
+ entries.sort((left, right) => right.quality - left.quality || left.index - right.index)
54
+ return entries.map((entry) => entry.tag)
55
+ }
56
+
57
+ function baseLanguage(locale: string): string {
58
+ return locale.toLowerCase().split('-')[0]
59
+ }
60
+
61
+ export function matchSupportedLocale(candidate: string, supportedLocales: string[]): string | null {
62
+ const lowered = candidate.toLowerCase()
63
+ const exact = supportedLocales.find((locale) => locale.toLowerCase() === lowered)
64
+ if (exact) return exact
65
+ const base = baseLanguage(candidate)
66
+ const baseExact = supportedLocales.find((locale) => locale.toLowerCase() === base)
67
+ if (baseExact) return baseExact
68
+ return supportedLocales.find((locale) => baseLanguage(locale) === base) ?? null
69
+ }
70
+
71
+ export function resolveStoreLocale(
72
+ store: StoreLocaleSettings,
73
+ candidates: StoreLocaleCandidates,
74
+ ): StoreLocaleResolution {
75
+ const queryLocale = sanitizeLocaleTag(candidates.queryLocale)
76
+ const headerLocale = sanitizeLocaleTag(candidates.headerLocale)
77
+ const ordered = [
78
+ ...(queryLocale ? [queryLocale] : []),
79
+ ...(headerLocale ? [headerLocale] : []),
80
+ ...parseAcceptLanguage(candidates.acceptLanguage),
81
+ ]
82
+ const requestedLocale = queryLocale ?? headerLocale ?? null
83
+ for (const candidate of ordered) {
84
+ const matched = matchSupportedLocale(candidate, store.supportedLocales)
85
+ if (matched) return { effectiveLocale: matched, requestedLocale }
86
+ }
87
+ return { effectiveLocale: store.defaultLocale, requestedLocale }
88
+ }
@@ -0,0 +1,55 @@
1
+ import { buildFeatureNotificationFromType } from '../../notifications/lib/notificationBuilder'
2
+ import type { NotificationService } from '../../notifications/lib/notificationService'
3
+ import { E } from '#generated/entities.ids.generated'
4
+ import { notificationTypes } from '../notifications'
5
+ import {
6
+ asPayloadRecord,
7
+ readEventScope,
8
+ readPayloadString,
9
+ tryResolveService,
10
+ type EcommerceSubscriberContext,
11
+ } from './subscriberSupport'
12
+
13
+ /**
14
+ * Store-manager alerts (SPEC-029 §9.4) delivered through `NotificationService.createForFeature`.
15
+ * Emitters already throttle per hour; the `groupKey` (store + UTC hour) additionally makes a
16
+ * retried or duplicated delivery refresh the recipient's existing notification instead of adding one.
17
+ */
18
+
19
+ export const STORE_NOTIFICATION_FEATURE = 'ecommerce.stores.manage'
20
+
21
+ export type StoreNotificationType = 'ecommerce.store.channel_binding_missing' | 'ecommerce.store.assortment_empty'
22
+
23
+ type FeatureNotifier = Pick<NotificationService, 'createForFeature'>
24
+
25
+ export function storeNotificationGroupKey(storeId: string, now: Date): string {
26
+ return `${storeId}:${now.toISOString().slice(0, 13)}`
27
+ }
28
+
29
+ export async function notifyStoreManagers(
30
+ payload: unknown,
31
+ ctx: EcommerceSubscriberContext,
32
+ type: StoreNotificationType,
33
+ now: Date = new Date(),
34
+ ): Promise<boolean> {
35
+ const record = asPayloadRecord(payload)
36
+ const storeId = readPayloadString(record, 'storeId') ?? readPayloadString(record, 'id')
37
+ const { tenantId, organizationId } = readEventScope(record, ctx)
38
+ if (!storeId || !tenantId || !organizationId) return false
39
+ const notificationService = tryResolveService<FeatureNotifier>(ctx, 'notificationService')
40
+ if (!notificationService) return false
41
+ const typeDef = notificationTypes.find((definition) => definition.type === type)
42
+ if (!typeDef) return false
43
+ const input = buildFeatureNotificationFromType(typeDef, {
44
+ requiredFeature: STORE_NOTIFICATION_FEATURE,
45
+ sourceEntityType: E.ecommerce.ecommerce_store,
46
+ sourceEntityId: storeId,
47
+ linkHref: `/backend/config/ecommerce/${encodeURIComponent(storeId)}`,
48
+ groupKey: storeNotificationGroupKey(storeId, now),
49
+ })
50
+ await notificationService.createForFeature(
51
+ { ...input, restrictRecipientsToOrganization: true },
52
+ { tenantId, organizationId },
53
+ )
54
+ return true
55
+ }
@@ -0,0 +1,427 @@
1
+ import type { AwilixContainer } from 'awilix'
2
+ import type { EntityManager } from '@mikro-orm/postgresql'
3
+ import { sql } from 'kysely'
4
+ import {
5
+ availabilityItemKey,
6
+ resolveAvailability,
7
+ type AvailabilityItemResult,
8
+ type AvailabilityModuleConfigReader,
9
+ type AvailabilityState,
10
+ } from '@open-mercato/shared/lib/availability'
11
+ import { parseBooleanFromUnknown } from '@open-mercato/shared/lib/boolean'
12
+ import { findWithDecryption } from '@open-mercato/shared/lib/encryption/find'
13
+ import type { AssortmentScope, EffectiveAssortmentScope } from '@open-mercato/shared/lib/catalog-visibility'
14
+ import { createLogger } from '@open-mercato/shared/lib/logger'
15
+ import { getTelemetryRuntime } from '@open-mercato/shared/lib/telemetry/runtime'
16
+ import { AttachmentPartition } from '@open-mercato/core/modules/attachments/data/entities'
17
+ import { CatalogProductCategory } from '@open-mercato/core/modules/catalog/data/entities'
18
+ import { batchLoadTranslationsMany } from '@open-mercato/core/modules/translations/lib/batch'
19
+ import type { StorefrontPrice, StorefrontPriceRange, StorefrontProductPricing } from './storefrontPricing'
20
+ import type { StoreContext } from './types'
21
+
22
+ const logger = createLogger('ecommerce')
23
+
24
+ export const STOREFRONT_PRODUCT_ENTITY_TYPE = 'catalog:catalog_product'
25
+ export const STOREFRONT_VARIANT_ENTITY_TYPE = 'catalog:catalog_product_variant'
26
+ export const STOREFRONT_CATEGORY_ENTITY_TYPE = 'catalog:catalog_product_category'
27
+ export const STOREFRONT_TAG_ENTITY_TYPE = 'catalog:catalog_product_tag'
28
+ export const STOREFRONT_OPTION_SCHEMA_ENTITY_TYPE = 'catalog:catalog_option_schema_template'
29
+
30
+ export type StorefrontAvailability = {
31
+ state: AvailabilityState
32
+ canFulfil: boolean
33
+ leadTimeDays: number | null
34
+ releaseAt: string | null
35
+ }
36
+
37
+ export type StorefrontProductListItem = {
38
+ id: string
39
+ handle: string | null
40
+ title: string
41
+ subtitle: string | null
42
+ defaultMediaUrl: string | null
43
+ productType: string
44
+ isConfigurable: boolean
45
+ hasVariants: boolean
46
+ variantCount: number
47
+ categories: Array<{ id: string; name: string; slug: string | null }>
48
+ tags: string[]
49
+ price: StorefrontPrice | null
50
+ priceRange: StorefrontPriceRange | null
51
+ availability: StorefrontAvailability
52
+ badges: string[]
53
+ }
54
+
55
+ export type CategoryLineage = { id: string; ancestorIds: string[]; descendantIds: string[] }
56
+
57
+ export type CategoryRef = { id: string; name: string; slug: string | null }
58
+
59
+ export type TagRef = { id: string; label: string }
60
+
61
+ export type TranslationOverlay = Record<string, Record<string, unknown>>
62
+
63
+ export type TranslationMap = Map<string, TranslationOverlay>
64
+
65
+ export type StorefrontPolicyResolutionScope = {
66
+ tenantId: string
67
+ organizationId: string
68
+ storeId: string | null
69
+ productId: string
70
+ variantId: string | null
71
+ }
72
+
73
+ type ResolvedPolicyField<T> = { value: T } | null | undefined
74
+
75
+ export type StorefrontResolvedPolicy = {
76
+ hideWhenOutOfStock?: ResolvedPolicyField<boolean>
77
+ minOrderQuantity?: ResolvedPolicyField<number | null>
78
+ maxOrderQuantity?: ResolvedPolicyField<number | null>
79
+ quantityIncrement?: ResolvedPolicyField<number | null>
80
+ }
81
+
82
+ export type StorefrontPolicyResolver = {
83
+ resolveMany(
84
+ em: EntityManager,
85
+ scopes: StorefrontPolicyResolutionScope[],
86
+ ): Promise<Array<StorefrontResolvedPolicy | null | undefined>>
87
+ }
88
+
89
+ export type StorefrontAvailabilityTarget = { productId: string; variantId: string | null }
90
+
91
+ export type ResolvedStorefrontAvailability = {
92
+ result: AvailabilityItemResult | null
93
+ policy: StorefrontResolvedPolicy | null
94
+ }
95
+
96
+ export function tryResolve<T>(container: AwilixContainer, name: string): T | null {
97
+ try {
98
+ return (container.resolve(name) as T | null | undefined) ?? null
99
+ } catch {
100
+ return null
101
+ }
102
+ }
103
+
104
+ export function referenceId(value: unknown): string | null {
105
+ if (typeof value === 'string') return value.length ? value : null
106
+ if (value && typeof value === 'object' && 'id' in value) {
107
+ const id = (value as { id: unknown }).id
108
+ return typeof id === 'string' && id.length ? id : null
109
+ }
110
+ return null
111
+ }
112
+
113
+ export function stringList(value: unknown): string[] {
114
+ return Array.isArray(value) ? value.filter((entry): entry is string => typeof entry === 'string' && entry.length > 0) : []
115
+ }
116
+
117
+ export function nonEmptyString(value: unknown): string | null {
118
+ return typeof value === 'string' && value.trim().length > 0 ? value : null
119
+ }
120
+
121
+ function categoryBranchAdmits(branch: AssortmentScope, lineage: Set<string>, reach: Set<string>): boolean {
122
+ const included = stringList(branch.categoryIds)
123
+ if (included.length > 0 && !included.some((id) => reach.has(id))) return false
124
+ if (stringList(branch.excludeCategoryIds).some((id) => lineage.has(id))) return false
125
+ return (branch.allOf ?? []).every((nested) => categoryBranchAdmits(nested, lineage, reach))
126
+ }
127
+
128
+ /**
129
+ * Whether a category can hold any product the effective assortment admits: one of its own subtree
130
+ * or ancestors is granted by some branch, and neither it nor an ancestor is excluded there. Tag and
131
+ * product conditions are product-level and do not decide a category's visibility.
132
+ */
133
+ export function isCategoryInAssortment(category: CategoryLineage, scope: EffectiveAssortmentScope): boolean {
134
+ if (scope === null) return true
135
+ const lineage = new Set([category.id, ...category.ancestorIds])
136
+ const reach = new Set([...lineage, ...category.descendantIds])
137
+ return scope.some((branch) => categoryBranchAdmits(branch, lineage, reach))
138
+ }
139
+
140
+ export type ActiveCategoryAncestor = { id: string; name: string; slug: string | null; ancestorIds: string[] }
141
+
142
+ /**
143
+ * Which of the given categories have every ancestor active and not deleted — the rule the category
144
+ * tree applies, so a category hidden there under an inactive parent is hidden everywhere. Ancestors
145
+ * listed in `known` (already checked active) need no lookup; the rest cost one categories query, and
146
+ * none when every category is a root or has only known ancestors. `ancestors` holds the active
147
+ * ancestor rows, fetched and known, for callers that render them (breadcrumbs).
148
+ */
149
+ export async function selectCategoriesWithActiveAncestors(
150
+ em: EntityManager,
151
+ ctx: Pick<StoreContext, 'tenantId' | 'organizationId'>,
152
+ categories: Array<{ id: string; ancestorIds: string[] }>,
153
+ known: { activeIds?: Iterable<string>; ancestors?: Map<string, ActiveCategoryAncestor> } = {},
154
+ ): Promise<{ visibleIds: Set<string>; ancestors: Map<string, ActiveCategoryAncestor> }> {
155
+ const ancestors = new Map(known.ancestors ?? [])
156
+ const active = new Set([...(known.activeIds ?? []), ...ancestors.keys()])
157
+ const unknown = Array.from(
158
+ new Set(categories.flatMap((category) => category.ancestorIds.filter((id) => !active.has(id)))),
159
+ )
160
+ if (unknown.length > 0) {
161
+ const rows = await findWithDecryption(
162
+ em,
163
+ CatalogProductCategory,
164
+ { id: { $in: unknown }, tenantId: ctx.tenantId, organizationId: ctx.organizationId, deletedAt: null, isActive: true },
165
+ { fields: ['id', 'name', 'slug', 'ancestorIds'] },
166
+ { tenantId: ctx.tenantId, organizationId: ctx.organizationId },
167
+ )
168
+ for (const row of rows) {
169
+ active.add(row.id)
170
+ ancestors.set(row.id, { id: row.id, name: row.name, slug: row.slug ?? null, ancestorIds: stringList(row.ancestorIds) })
171
+ }
172
+ }
173
+ const visibleIds = new Set(
174
+ categories.filter((category) => category.ancestorIds.every((id) => active.has(id))).map((category) => category.id),
175
+ )
176
+ return { visibleIds, ancestors }
177
+ }
178
+
179
+ export function localize(
180
+ base: unknown,
181
+ translations: TranslationOverlay | undefined,
182
+ field: string,
183
+ locales: string[],
184
+ ): string | null {
185
+ for (const locale of locales) {
186
+ const value = nonEmptyString(translations?.[locale]?.[field])
187
+ if (value !== null) return value
188
+ }
189
+ return nonEmptyString(base)
190
+ }
191
+
192
+ export function localeChain(ctx: StoreContext, preferredLocale?: string | null): string[] {
193
+ const head = preferredLocale && ctx.store.supportedLocales.includes(preferredLocale) ? preferredLocale : ctx.effectiveLocale
194
+ return Array.from(new Set([head, ctx.store.defaultLocale].filter((locale): locale is string => !!locale)))
195
+ }
196
+
197
+ export function toStorefrontAvailability(result: AvailabilityItemResult | null): StorefrontAvailability {
198
+ if (!result) return { state: 'not_tracked', canFulfil: true, leadTimeDays: null, releaseAt: null }
199
+ return {
200
+ state: result.state,
201
+ canFulfil: result.canFulfil,
202
+ leadTimeDays: result.leadTimeDays,
203
+ releaseAt: result.releaseAt,
204
+ }
205
+ }
206
+
207
+ export function storefrontAvailabilityKey(target: StorefrontAvailabilityTarget): string {
208
+ return availabilityItemKey({ catalogProductId: target.productId, catalogVariantId: target.variantId })
209
+ }
210
+
211
+ /**
212
+ * One batched `resolveAvailability` (state) and one `policyResolutionService.resolveMany` (quantity
213
+ * rules, `hideWhenOutOfStock`) for every product and variant of a response, keyed by
214
+ * `storefrontAvailabilityKey`. A missing policy service yields `policy: null`.
215
+ */
216
+ export async function resolveStorefrontAvailability(
217
+ container: AwilixContainer,
218
+ ctx: StoreContext,
219
+ em: EntityManager,
220
+ targets: StorefrontAvailabilityTarget[],
221
+ ): Promise<Map<string, ResolvedStorefrontAvailability>> {
222
+ const results = new Map<string, ResolvedStorefrontAvailability>()
223
+ if (!targets.length) return results
224
+ const items = targets.map((target) => ({
225
+ catalogProductId: target.productId,
226
+ catalogVariantId: target.variantId,
227
+ quantity: 1,
228
+ }))
229
+ const moduleConfig = tryResolve<AvailabilityModuleConfigReader>(container, 'moduleConfigService')
230
+ const policyResolver = tryResolve<StorefrontPolicyResolver>(container, 'policyResolutionService')
231
+ const [availability, policies] = await Promise.all([
232
+ resolveAvailability(
233
+ {
234
+ tenantId: ctx.tenantId,
235
+ organizationId: ctx.organizationId,
236
+ storeId: ctx.store.id,
237
+ channelId: ctx.channel?.salesChannelId ?? null,
238
+ items,
239
+ },
240
+ moduleConfig ? { moduleConfig, container } : { container },
241
+ ),
242
+ policyResolver
243
+ ? policyResolver.resolveMany(
244
+ em,
245
+ targets.map((target) => ({
246
+ tenantId: ctx.tenantId,
247
+ organizationId: ctx.organizationId,
248
+ storeId: ctx.store.id,
249
+ productId: target.productId,
250
+ variantId: target.variantId,
251
+ })),
252
+ )
253
+ : Promise.resolve([]),
254
+ ])
255
+ targets.forEach((target, index) => {
256
+ const key = storefrontAvailabilityKey(target)
257
+ results.set(key, { result: availability.byItem[key] ?? null, policy: policies[index] ?? null })
258
+ })
259
+ return results
260
+ }
261
+
262
+ /**
263
+ * The response's translation overlays in one query (Storefront Public API §7.2: one call per
264
+ * response), keyed by entity type. A failure degrades to base fields and is reported.
265
+ */
266
+ export async function loadStorefrontTranslations(
267
+ em: EntityManager,
268
+ ctx: StoreContext,
269
+ requests: Array<{ entityType: string; ids: string[] }>,
270
+ ): Promise<Map<string, TranslationMap>> {
271
+ const pending = requests.filter((request) => request.ids.length > 0)
272
+ if (!pending.length) return new Map()
273
+ try {
274
+ return await batchLoadTranslationsMany(
275
+ em.getKysely(),
276
+ pending.map((request) => ({ entityType: request.entityType, entityIds: request.ids })),
277
+ { tenantId: ctx.tenantId, organizationId: ctx.organizationId },
278
+ )
279
+ } catch (err) {
280
+ logger.error('[internal] ecommerce storefront translation overlay failed', {
281
+ tenantId: ctx.tenantId,
282
+ organizationId: ctx.organizationId,
283
+ storeId: ctx.store.id,
284
+ err,
285
+ })
286
+ getTelemetryRuntime()?.reportError(err, {
287
+ module: 'ecommerce',
288
+ code: 'ecommerce.storefront_translations_failed',
289
+ attributes: { entityTypes: pending.length },
290
+ })
291
+ return new Map()
292
+ }
293
+ }
294
+
295
+ export function isImageMimeType(mimeType: string | null | undefined): boolean {
296
+ return typeof mimeType === 'string' && mimeType.trim().toLowerCase().startsWith('image/')
297
+ }
298
+
299
+ /** The codes among `codes` naming a public attachment partition visible to the store's tenant and organization. */
300
+ export async function loadPublicPartitionCodes(em: EntityManager, ctx: StoreContext, codes: string[]): Promise<Set<string>> {
301
+ if (!codes.length) return new Set()
302
+ const partitions = await findWithDecryption(
303
+ em,
304
+ AttachmentPartition,
305
+ {
306
+ code: { $in: codes },
307
+ isPublic: true,
308
+ $and: [
309
+ { $or: [{ tenantId: null }, { tenantId: ctx.tenantId }] },
310
+ { $or: [{ organizationId: null }, { organizationId: ctx.organizationId }] },
311
+ ],
312
+ },
313
+ { fields: ['id', 'code'] },
314
+ { tenantId: ctx.tenantId, organizationId: ctx.organizationId },
315
+ )
316
+ return new Set(partitions.map((partition) => partition.code))
317
+ }
318
+
319
+ export type StorefrontDefaultMediaRef = { id: string; defaultMediaId?: unknown; defaultMediaUrl?: unknown }
320
+
321
+ type PublicMediaDatabase = {
322
+ attachments: {
323
+ id: string
324
+ tenant_id: string | null
325
+ organization_id: string | null
326
+ partition_code: string
327
+ mime_type: string
328
+ }
329
+ attachment_partitions: {
330
+ code: string
331
+ is_public: boolean
332
+ tenant_id: string | null
333
+ organization_id: string | null
334
+ }
335
+ }
336
+
337
+ /**
338
+ * Card `defaultMediaUrl`s held to the gallery rule of the product detail: a product's stored URL is
339
+ * returned only while its `defaultMediaId` names an `image/*` attachment of the store's tenant and
340
+ * organization in a public partition; otherwise it is `null`. One query for the whole page (the
341
+ * attachments joined to their partitions, plain non-encrypted columns), none when no product
342
+ * carries a default media id.
343
+ */
344
+ export async function resolvePublicDefaultMediaUrls(
345
+ em: EntityManager,
346
+ ctx: StoreContext,
347
+ refs: StorefrontDefaultMediaRef[],
348
+ ): Promise<Map<string, string | null>> {
349
+ const urls = new Map<string, string | null>()
350
+ const pending: Array<{ productId: string; mediaId: string; url: string }> = []
351
+ for (const ref of refs) {
352
+ urls.set(ref.id, null)
353
+ const url = nonEmptyString(ref.defaultMediaUrl)
354
+ const mediaId = nonEmptyString(ref.defaultMediaId)
355
+ if (url && mediaId) pending.push({ productId: ref.id, mediaId, url })
356
+ }
357
+ if (!pending.length) return urls
358
+ const mediaIds = Array.from(new Set(pending.map((entry) => entry.mediaId)))
359
+ const rows = await em
360
+ .getKysely<PublicMediaDatabase>()
361
+ .selectFrom('attachments as a')
362
+ .innerJoin('attachment_partitions as p', 'p.code', 'a.partition_code')
363
+ .select(['a.id as id', 'a.mime_type as mime_type'])
364
+ .where(sql<boolean>`${sql.ref('a.id')} = any(${mediaIds}::uuid[])`)
365
+ .where('a.tenant_id', '=', ctx.tenantId)
366
+ .where('a.organization_id', '=', ctx.organizationId)
367
+ .where('p.is_public', '=', true)
368
+ .where((eb) => eb.or([eb('p.tenant_id', 'is', null), eb('p.tenant_id', '=', ctx.tenantId)]))
369
+ .where((eb) => eb.or([eb('p.organization_id', 'is', null), eb('p.organization_id', '=', ctx.organizationId)]))
370
+ .execute()
371
+ const publicIds = new Set(rows.filter((row) => isImageMimeType(row.mime_type)).map((row) => row.id))
372
+ for (const entry of pending) if (publicIds.has(entry.mediaId)) urls.set(entry.productId, entry.url)
373
+ return urls
374
+ }
375
+
376
+ export type StorefrontListItemSource = {
377
+ id: string
378
+ handle?: unknown
379
+ title?: unknown
380
+ subtitle?: unknown
381
+ defaultMediaUrl?: unknown
382
+ productType?: unknown
383
+ isConfigurable?: unknown
384
+ }
385
+
386
+ export type StorefrontListItemParts = {
387
+ locales: string[]
388
+ overlay?: TranslationOverlay
389
+ variantCount: number
390
+ categories: CategoryRef[]
391
+ categoryTranslations?: TranslationMap
392
+ tags: TagRef[]
393
+ tagTranslations?: TranslationMap
394
+ pricing?: StorefrontProductPricing
395
+ availability: StorefrontAvailability
396
+ }
397
+
398
+ export function buildStorefrontListItem(
399
+ source: StorefrontListItemSource,
400
+ parts: StorefrontListItemParts,
401
+ ): StorefrontProductListItem {
402
+ const { locales, overlay } = parts
403
+ const price = parts.pricing?.price ?? null
404
+ return {
405
+ id: source.id,
406
+ handle: nonEmptyString(source.handle),
407
+ title: localize(source.title, overlay, 'title', locales) ?? '',
408
+ subtitle: localize(source.subtitle, overlay, 'subtitle', locales),
409
+ defaultMediaUrl: nonEmptyString(source.defaultMediaUrl),
410
+ productType: nonEmptyString(source.productType) ?? 'simple',
411
+ isConfigurable: parseBooleanFromUnknown(source.isConfigurable) === true,
412
+ hasVariants: parts.variantCount > 0,
413
+ variantCount: parts.variantCount,
414
+ categories: parts.categories.map((category) => ({
415
+ id: category.id,
416
+ name: localize(category.name, parts.categoryTranslations?.get(category.id), 'name', locales) ?? category.name,
417
+ slug: category.slug,
418
+ })),
419
+ tags: parts.tags.map(
420
+ (tag) => localize(tag.label, parts.tagTranslations?.get(tag.id), 'label', locales) ?? tag.label,
421
+ ),
422
+ price,
423
+ priceRange: parts.pricing?.priceRange ?? null,
424
+ availability: parts.availability,
425
+ badges: price?.isPromotion ? ['sale'] : [],
426
+ }
427
+ }