@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,454 @@
1
+ import type { AwilixContainer } from 'awilix'
2
+ import type { EntityManager } from '@mikro-orm/postgresql'
3
+ import { findWithDecryption } from '@open-mercato/shared/lib/encryption/find'
4
+ import { createLogger } from '@open-mercato/shared/lib/logger'
5
+ import { sanitizeSearchTerm } from '@open-mercato/shared/lib/query/sanitizeSearchTerm'
6
+ import { SortDir, type QueryEngine } from '@open-mercato/shared/lib/query/types'
7
+ import { resolveSearchConfig } from '@open-mercato/shared/lib/search/config'
8
+ import { tokenizeText } from '@open-mercato/shared/lib/search/tokenize'
9
+ import { getTelemetryRuntime } from '@open-mercato/shared/lib/telemetry/runtime'
10
+ import type { SearchStrategy } from '@open-mercato/shared/modules/search'
11
+ import { E } from '#generated/entities.ids.generated'
12
+ import { CatalogProductVariant } from '@open-mercato/core/modules/catalog/data/entities'
13
+ import { scoreProductSearchRelevance } from '@open-mercato/core/modules/catalog/lib/productFilters'
14
+ import { compareCodeUnits } from '@open-mercato/core/modules/catalog/lib/productScopeKeys'
15
+ import {
16
+ ECOMMERCE_STOREFRONT_SUGGEST_MIN_QUERY_LENGTH,
17
+ type EcommerceStorefrontSearchSuggestQuery,
18
+ } from '../data/validators'
19
+ import {
20
+ STOREFRONT_CATEGORY_ENTITY_TYPE,
21
+ STOREFRONT_PRODUCT_ENTITY_TYPE,
22
+ loadStorefrontTranslations,
23
+ localeChain,
24
+ localize,
25
+ nonEmptyString,
26
+ referenceId,
27
+ resolvePublicDefaultMediaUrls,
28
+ resolveStorefrontAvailability,
29
+ storefrontAvailabilityKey,
30
+ toStorefrontAvailability,
31
+ tryResolve,
32
+ } from './storefrontCatalogSupport'
33
+ import { loadStorefrontVisibleCategories, type StorefrontVisibleCategory } from './storefrontCategories'
34
+ import { resolveStorefrontPrices, type StorefrontProductPricing } from './storefrontPricing'
35
+ import {
36
+ buildStorefrontProductScope,
37
+ buildStorefrontSearchIndexDocFilter,
38
+ composeStorefrontProductFilters,
39
+ type StorefrontProductScope,
40
+ } from './storefrontProductScope'
41
+ import { buildStorefrontProductFilterClause } from './storefrontProducts'
42
+ import type { StoreContext } from './types'
43
+
44
+ /**
45
+ * `GET /search/suggest` (Storefront Public API rev 4 §4.5, §8).
46
+ *
47
+ * Products are ranked by one backend, chosen per request by `selectStorefrontSearchBackend`:
48
+ * the `tokens` strategy or the `vector` strategy on the `pgvector` driver of `@open-mercato/search`
49
+ * (D19), each receiving `buildStorefrontSearchIndexDocFilter` so the assortment scope is evaluated
50
+ * inside the ranking query and a restricted buyer is never starved by out-of-scope hits (§8.2,
51
+ * R14); otherwise the Phase 1 `ILIKE` path (§8.1) over the scoped catalogue. Meilisearch,
52
+ * `chromadb` and `qdrant` cannot evaluate the scope and are never used. Whatever ranked them, the
53
+ * products are re-read through `buildStorefrontProductScope`, priced for the buyer, and hidden when
54
+ * their policy hides out-of-stock products, exactly like the listing page. The payload is the same
55
+ * for every backend (§8.3); facets are never computed.
56
+ *
57
+ * Categories match the localized name of the buyer's visible categories (the `/categories`
58
+ * visibility rule) and are independent of the backend. `suggestions` stays empty in this phase.
59
+ */
60
+
61
+ export type StorefrontSearchBackendPreference = 'auto' | 'ilike' | 'tokens' | 'vector'
62
+
63
+ export type StorefrontSearchBackend = 'ilike' | 'tokens' | 'vector'
64
+
65
+ export type StorefrontSearchBackendSelection =
66
+ | { backend: 'ilike' }
67
+ | { backend: 'tokens' | 'vector'; strategy: SearchStrategy }
68
+
69
+ export type StorefrontSearchSuggestProduct = {
70
+ id: string
71
+ handle: string | null
72
+ title: string
73
+ defaultMediaUrl: string | null
74
+ formattedPrice: string | null
75
+ }
76
+
77
+ export type StorefrontSearchSuggestCategory = { id: string; name: string; slug: string | null }
78
+
79
+ export type StorefrontSearchSuggestResponse = {
80
+ products: StorefrontSearchSuggestProduct[]
81
+ categories: StorefrontSearchSuggestCategory[]
82
+ suggestions: string[]
83
+ effectiveLocale: string
84
+ }
85
+
86
+ export type SuggestStorefrontSearchOptions = {
87
+ /** Overrides `OM_ECOMMERCE_STOREFRONT_SEARCH`; intended for tests. */
88
+ backendPreference?: StorefrontSearchBackendPreference
89
+ date?: Date
90
+ }
91
+
92
+ /** `auto` (default): `tokens`, then `pgvector`, then `ILIKE`; any other value forces that backend or `ILIKE`. */
93
+ export const STOREFRONT_SEARCH_BACKEND_ENV = 'OM_ECOMMERCE_STOREFRONT_SEARCH'
94
+
95
+ /** `ILIKE` candidates read (title order) before relevance ranking picks the `limit` best. */
96
+ export const STOREFRONT_SUGGEST_ILIKE_WINDOW = 100
97
+
98
+ const AUTO_ORDER: readonly StorefrontSearchBackend[] = ['tokens', 'vector']
99
+
100
+ const logger = createLogger('ecommerce').child({ component: 'storefront-search' })
101
+
102
+ type StrategyRegistry = {
103
+ getStrategy(strategyId: string): SearchStrategy | undefined
104
+ isStrategyAvailable(strategyId: string): Promise<boolean>
105
+ }
106
+
107
+ type Runtime = {
108
+ container: AwilixContainer
109
+ ctx: StoreContext
110
+ em: EntityManager
111
+ queryEngine: QueryEngine
112
+ scope: StorefrontProductScope
113
+ decryptionScope: { tenantId: string; organizationId: string }
114
+ date: Date
115
+ }
116
+
117
+ type RankedProduct = { id: string; score: number }
118
+
119
+ type ProductRecord = {
120
+ id: string
121
+ title?: string | null
122
+ sku?: string | null
123
+ handle?: string | null
124
+ default_media_id?: string | null
125
+ default_media_url?: string | null
126
+ }
127
+
128
+ type OrderedProduct = { record: ProductRecord; score: number; relevance: number; title: string }
129
+
130
+ export function readStorefrontSearchBackendPreference(
131
+ value: string | undefined = process.env[STOREFRONT_SEARCH_BACKEND_ENV],
132
+ ): StorefrontSearchBackendPreference {
133
+ const token = value?.trim().toLowerCase()
134
+ if (token === 'ilike' || token === 'tokens' || token === 'vector') return token
135
+ return 'auto'
136
+ }
137
+
138
+ function isStrategyRegistry(value: unknown): value is StrategyRegistry {
139
+ if (!value || typeof value !== 'object') return false
140
+ const candidate = value as Partial<StrategyRegistry>
141
+ return typeof candidate.getStrategy === 'function' && typeof candidate.isStrategyAvailable === 'function'
142
+ }
143
+
144
+ function tokensCanRank(term: string): boolean {
145
+ const config = resolveSearchConfig()
146
+ return config.enabled && tokenizeText(term, config).hashes.length > 0
147
+ }
148
+
149
+ async function isAvailable(registry: StrategyRegistry, strategyId: string): Promise<boolean> {
150
+ try {
151
+ return await registry.isStrategyAvailable(strategyId)
152
+ } catch {
153
+ return false
154
+ }
155
+ }
156
+
157
+ /**
158
+ * The ranking backend for one search term (§8.2, D19). A search strategy qualifies only when it is
159
+ * registered, available and declares `supportsIndexDocFilter` — the `tokens` strategy, and the
160
+ * `vector` strategy over `pgvector`; `tokens` additionally needs search enabled and a term that
161
+ * tokenizes (shorter than the minimum token length it does not). Anything else is `ILIKE`.
162
+ */
163
+ export async function selectStorefrontSearchBackend(
164
+ container: AwilixContainer,
165
+ term: string,
166
+ preference: StorefrontSearchBackendPreference = readStorefrontSearchBackendPreference(),
167
+ ): Promise<StorefrontSearchBackendSelection> {
168
+ if (preference === 'ilike') return { backend: 'ilike' }
169
+ const registry = tryResolve<unknown>(container, 'searchService')
170
+ if (!isStrategyRegistry(registry)) return { backend: 'ilike' }
171
+ const order = preference === 'auto' ? AUTO_ORDER : [preference]
172
+ for (const backend of order) {
173
+ if (backend === 'ilike') continue
174
+ const strategy = registry.getStrategy(backend)
175
+ if (!strategy || strategy.supportsIndexDocFilter !== true) continue
176
+ if (backend === 'tokens' && !tokensCanRank(term)) continue
177
+ if (!(await isAvailable(registry, backend))) continue
178
+ return { backend, strategy }
179
+ }
180
+ return { backend: 'ilike' }
181
+ }
182
+
183
+ function normalizeForMatch(value: string): string {
184
+ return value.normalize('NFKD').replace(/[̀-ͯ]/g, '').toLowerCase()
185
+ }
186
+
187
+ function compareOrdered(left: OrderedProduct, right: OrderedProduct): number {
188
+ return (
189
+ right.score - left.score ||
190
+ left.relevance - right.relevance ||
191
+ left.title.localeCompare(right.title) ||
192
+ compareCodeUnits(left.record.id, right.record.id)
193
+ )
194
+ }
195
+
196
+ async function rankWithStrategy(
197
+ runtime: Runtime,
198
+ strategy: SearchStrategy,
199
+ term: string,
200
+ limit: number,
201
+ ): Promise<RankedProduct[]> {
202
+ const { ctx } = runtime
203
+ const results = await strategy.search(term, {
204
+ tenantId: ctx.tenantId,
205
+ organizationId: ctx.organizationId,
206
+ entityTypes: [E.catalog.catalog_product],
207
+ limit,
208
+ indexDocFilter: buildStorefrontSearchIndexDocFilter(ctx),
209
+ })
210
+ const seen = new Set<string>()
211
+ const ranked: RankedProduct[] = []
212
+ for (const result of results) {
213
+ if (result.entityId !== E.catalog.catalog_product || seen.has(result.recordId)) continue
214
+ seen.add(result.recordId)
215
+ ranked.push({ id: result.recordId, score: Number.isFinite(result.score) ? result.score : 0 })
216
+ }
217
+ return ranked.slice(0, limit)
218
+ }
219
+
220
+ async function rankWithIlike(runtime: Runtime, term: string, limit: number): Promise<RankedProduct[]> {
221
+ const { scope } = runtime
222
+ const clause = await buildStorefrontProductFilterClause(runtime.container, runtime.ctx, { search: term })
223
+ const result = await runtime.queryEngine.query<ProductRecord>(E.catalog.catalog_product, {
224
+ tenantId: scope.tenantId,
225
+ organizationId: scope.organizationId,
226
+ withDeleted: scope.withDeleted,
227
+ filters: composeStorefrontProductFilters(scope, clause),
228
+ fields: ['id', 'title', 'sku'],
229
+ sort: [{ field: 'title', dir: SortDir.Asc }],
230
+ page: { page: 1, pageSize: STOREFRONT_SUGGEST_ILIKE_WINDOW },
231
+ })
232
+ const needle = term.toLowerCase()
233
+ return result.items
234
+ .filter((item) => typeof item.id === 'string')
235
+ .map((record) => ({
236
+ record,
237
+ score: 0,
238
+ relevance: scoreProductSearchRelevance(needle, record.title, record.sku),
239
+ title: nonEmptyString(record.title) ?? '',
240
+ }))
241
+ .sort(compareOrdered)
242
+ .slice(0, limit)
243
+ .map((entry) => ({ id: entry.record.id, score: 0 }))
244
+ }
245
+
246
+ async function rankProducts(
247
+ runtime: Runtime,
248
+ selection: StorefrontSearchBackendSelection,
249
+ term: string,
250
+ limit: number,
251
+ ): Promise<RankedProduct[]> {
252
+ if (selection.backend === 'ilike') return rankWithIlike(runtime, term, limit)
253
+ try {
254
+ return await rankWithStrategy(runtime, selection.strategy, term, limit)
255
+ } catch (err) {
256
+ logger.error('[internal] ecommerce storefront search backend failed; falling back to ILIKE', {
257
+ backend: selection.backend,
258
+ tenantId: runtime.ctx.tenantId,
259
+ storeId: runtime.ctx.store.id,
260
+ err,
261
+ })
262
+ getTelemetryRuntime()?.reportError(err, {
263
+ module: 'ecommerce',
264
+ code: 'ecommerce.storefront_search_backend_failed',
265
+ attributes: { backend: selection.backend },
266
+ })
267
+ return rankWithIlike(runtime, term, limit)
268
+ }
269
+ }
270
+
271
+ async function loadProductsByIds(runtime: Runtime, ids: string[]): Promise<ProductRecord[]> {
272
+ if (!ids.length) return []
273
+ const { scope } = runtime
274
+ const result = await runtime.queryEngine.query<ProductRecord>(E.catalog.catalog_product, {
275
+ tenantId: scope.tenantId,
276
+ organizationId: scope.organizationId,
277
+ withDeleted: scope.withDeleted,
278
+ filters: composeStorefrontProductFilters(scope, { id: { $in: ids } }),
279
+ fields: ['id', 'title', 'sku', 'handle', 'default_media_id', 'default_media_url'],
280
+ page: { page: 1, pageSize: ids.length },
281
+ })
282
+ return result.items.filter((item) => typeof item.id === 'string')
283
+ }
284
+
285
+ async function loadVariantIds(runtime: Runtime, productIds: string[]): Promise<Map<string, string[]>> {
286
+ const byProduct = new Map<string, string[]>()
287
+ if (!productIds.length) return byProduct
288
+ const { ctx } = runtime
289
+ const variants = await findWithDecryption(
290
+ runtime.em,
291
+ CatalogProductVariant,
292
+ {
293
+ tenantId: ctx.tenantId,
294
+ organizationId: ctx.organizationId,
295
+ deletedAt: null,
296
+ isActive: true,
297
+ product: { $in: productIds },
298
+ },
299
+ { fields: ['id', 'product'] },
300
+ runtime.decryptionScope,
301
+ )
302
+ for (const variant of variants) {
303
+ const productId = referenceId(variant.product)
304
+ if (!productId) continue
305
+ byProduct.set(productId, [...(byProduct.get(productId) ?? []), variant.id])
306
+ }
307
+ return byProduct
308
+ }
309
+
310
+ async function loadPricing(runtime: Runtime, productIds: string[]): Promise<Map<string, StorefrontProductPricing>> {
311
+ if (!productIds.length) return new Map()
312
+ const variants = await loadVariantIds(runtime, productIds)
313
+ return resolveStorefrontPrices(
314
+ runtime.container,
315
+ runtime.ctx,
316
+ productIds.map((productId) => ({ productId, variantIds: variants.get(productId) ?? [] })),
317
+ { date: runtime.date },
318
+ )
319
+ }
320
+
321
+ async function loadHiddenProductIds(runtime: Runtime, productIds: string[]): Promise<Set<string>> {
322
+ const hidden = new Set<string>()
323
+ if (!productIds.length) return hidden
324
+ const targets = productIds.map((productId) => ({ productId, variantId: null }))
325
+ const resolved = await resolveStorefrontAvailability(runtime.container, runtime.ctx, runtime.em, targets)
326
+ for (const target of targets) {
327
+ const entry = resolved.get(storefrontAvailabilityKey(target))
328
+ if (entry?.policy?.hideWhenOutOfStock?.value !== true) continue
329
+ if (toStorefrontAvailability(entry.result ?? null).state === 'out_of_stock') hidden.add(target.productId)
330
+ }
331
+ return hidden
332
+ }
333
+
334
+ function formattedPrice(pricing: StorefrontProductPricing | undefined): string | null {
335
+ return pricing?.price?.formatted ?? pricing?.priceRange?.formattedMin ?? null
336
+ }
337
+
338
+ function matchCategories(
339
+ categories: Map<string, StorefrontVisibleCategory>,
340
+ translations: Map<string, Record<string, Record<string, unknown>>> | undefined,
341
+ locales: string[],
342
+ term: string,
343
+ limit: number,
344
+ ): StorefrontSearchSuggestCategory[] {
345
+ const needle = normalizeForMatch(term)
346
+ const matches: Array<{ category: StorefrontSearchSuggestCategory; relevance: number }> = []
347
+ for (const row of categories.values()) {
348
+ const name = localize(row.name, translations?.get(row.id), 'name', locales) ?? row.name
349
+ const normalized = normalizeForMatch(name)
350
+ if (!normalized.includes(needle)) continue
351
+ matches.push({
352
+ category: { id: row.id, name, slug: row.slug },
353
+ relevance: scoreProductSearchRelevance(needle, normalized, null),
354
+ })
355
+ }
356
+ return matches
357
+ .sort(
358
+ (left, right) =>
359
+ left.relevance - right.relevance ||
360
+ left.category.name.localeCompare(right.category.name) ||
361
+ compareCodeUnits(left.category.id, right.category.id),
362
+ )
363
+ .slice(0, limit)
364
+ .map((entry) => entry.category)
365
+ }
366
+
367
+ function emptyResponse(ctx: StoreContext): StorefrontSearchSuggestResponse {
368
+ return { products: [], categories: [], suggestions: [], effectiveLocale: ctx.effectiveLocale }
369
+ }
370
+
371
+ /** The normalized term the suggest response is computed and cached for; `''` when shorter than the minimum. */
372
+ export function normalizeStorefrontSuggestTerm(q: string): string {
373
+ const term = sanitizeSearchTerm(q).replace(/\s+/g, ' ')
374
+ return Array.from(term).length >= ECOMMERCE_STOREFRONT_SUGGEST_MIN_QUERY_LENGTH ? term : ''
375
+ }
376
+
377
+ export async function suggestStorefrontSearch(
378
+ container: AwilixContainer,
379
+ ctx: StoreContext,
380
+ query: Pick<EcommerceStorefrontSearchSuggestQuery, 'q' | 'limit'>,
381
+ options: SuggestStorefrontSearchOptions = {},
382
+ ): Promise<StorefrontSearchSuggestResponse> {
383
+ const term = normalizeStorefrontSuggestTerm(query.q)
384
+ const assortment = ctx.buyer.assortmentScope
385
+ if (!term || (assortment !== null && assortment.length === 0)) return emptyResponse(ctx)
386
+ const em = tryResolve<EntityManager>(container, 'em')
387
+ const queryEngine = tryResolve<QueryEngine>(container, 'queryEngine')
388
+ if (!em || !queryEngine) throw new Error('[internal] ecommerce storefront search requires em and queryEngine')
389
+ const runtime: Runtime = {
390
+ container,
391
+ ctx,
392
+ em,
393
+ queryEngine,
394
+ scope: buildStorefrontProductScope(ctx),
395
+ decryptionScope: { tenantId: ctx.tenantId, organizationId: ctx.organizationId },
396
+ date: options.date ?? new Date(),
397
+ }
398
+ const selection = await selectStorefrontSearchBackend(container, term, options.backendPreference)
399
+ const [ranked, categories] = await Promise.all([
400
+ rankProducts(runtime, selection, term, query.limit),
401
+ loadStorefrontVisibleCategories(runtime),
402
+ ])
403
+ const rankedIds = ranked.map((entry) => entry.id)
404
+ const [records, translations, pricing, hidden] = await Promise.all([
405
+ loadProductsByIds(runtime, rankedIds),
406
+ loadStorefrontTranslations(em, ctx, [
407
+ { entityType: STOREFRONT_PRODUCT_ENTITY_TYPE, ids: rankedIds },
408
+ { entityType: STOREFRONT_CATEGORY_ENTITY_TYPE, ids: Array.from(categories.keys()) },
409
+ ]),
410
+ loadPricing(runtime, rankedIds),
411
+ loadHiddenProductIds(runtime, rankedIds),
412
+ ])
413
+ const visibleRecords = records.filter((record) => !hidden.has(record.id))
414
+ const mediaUrls = await resolvePublicDefaultMediaUrls(
415
+ em,
416
+ ctx,
417
+ visibleRecords.map((record) => ({
418
+ id: record.id,
419
+ defaultMediaId: record.default_media_id,
420
+ defaultMediaUrl: record.default_media_url,
421
+ })),
422
+ )
423
+ const locales = localeChain(ctx)
424
+ const productTranslations = translations.get(STOREFRONT_PRODUCT_ENTITY_TYPE)
425
+ const scores = new Map(ranked.map((entry) => [entry.id, entry.score]))
426
+ const needle = term.toLowerCase()
427
+ const products = visibleRecords
428
+ .map((record) => ({
429
+ record,
430
+ score: scores.get(record.id) ?? 0,
431
+ relevance: scoreProductSearchRelevance(needle, record.title, record.sku),
432
+ title: nonEmptyString(record.title) ?? '',
433
+ }))
434
+ .sort(compareOrdered)
435
+ .map(({ record }) => ({
436
+ id: record.id,
437
+ handle: nonEmptyString(record.handle),
438
+ title: localize(record.title, productTranslations?.get(record.id), 'title', locales) ?? '',
439
+ defaultMediaUrl: mediaUrls.get(record.id) ?? null,
440
+ formattedPrice: formattedPrice(pricing.get(record.id)),
441
+ }))
442
+ return {
443
+ products,
444
+ categories: matchCategories(
445
+ categories,
446
+ translations.get(STOREFRONT_CATEGORY_ENTITY_TYPE),
447
+ locales,
448
+ term,
449
+ query.limit,
450
+ ),
451
+ suggestions: [],
452
+ effectiveLocale: ctx.effectiveLocale,
453
+ }
454
+ }
@@ -0,0 +1,49 @@
1
+ import type { AwilixContainer } from 'awilix'
2
+ import type { EcommerceStorefrontSearchSuggestQuery } from '../data/validators'
3
+ import { catalogProductsTag, storefrontAvailabilityTag, storefrontCache, storefrontCacheValueHash } from './cacheKeys'
4
+ import {
5
+ normalizeStorefrontSuggestTerm,
6
+ suggestStorefrontSearch,
7
+ type StorefrontSearchSuggestResponse,
8
+ } from './storefrontSearch'
9
+ import type { StoreContext } from './types'
10
+
11
+ /**
12
+ * Server-side cache for `GET /search/suggest` (Storefront Public API rev 4 §9): `formattedPrice` is
13
+ * buyer-priced, so entries are keyed on the full `digest` plus the normalized term and `limit`.
14
+ * They carry the tenant's `catalog-products` tag (every product, variant, category and price event)
15
+ * and the availability tag (out-of-stock hiding). A term below the minimum length is answered
16
+ * empty without touching the cache.
17
+ */
18
+
19
+ export const STOREFRONT_SEARCH_SUGGEST_TTL_MS = 30_000
20
+
21
+ const SUGGEST_CACHE_SEGMENT = 'search-suggest'
22
+
23
+ export function storefrontSearchSuggestCacheParts(term: string, limit: number): string[] {
24
+ return [SUGGEST_CACHE_SEGMENT, storefrontCacheValueHash({ term: term.toLowerCase(), limit })]
25
+ }
26
+
27
+ export function storefrontSearchSuggestCacheTags(ctx: StoreContext): string[] {
28
+ return [catalogProductsTag(ctx.tenantId), storefrontAvailabilityTag(ctx.tenantId)]
29
+ }
30
+
31
+ export async function cachedSuggestStorefrontSearch(
32
+ container: AwilixContainer,
33
+ ctx: StoreContext,
34
+ query: Pick<EcommerceStorefrontSearchSuggestQuery, 'q' | 'limit'>,
35
+ ): Promise<StorefrontSearchSuggestResponse> {
36
+ const term = normalizeStorefrontSuggestTerm(query.q)
37
+ if (!term) return suggestStorefrontSearch(container, ctx, query)
38
+ const cache = storefrontCache(container, ctx)
39
+ const parts = storefrontSearchSuggestCacheParts(term, query.limit)
40
+ const cached = await cache.get<StorefrontSearchSuggestResponse>(parts, { scope: 'digest' })
41
+ if (cached) return cached
42
+ const response = await suggestStorefrontSearch(container, ctx, { q: term, limit: query.limit })
43
+ await cache.set(parts, response, {
44
+ scope: 'digest',
45
+ ttlMs: STOREFRONT_SEARCH_SUGGEST_TTL_MS,
46
+ tags: storefrontSearchSuggestCacheTags(ctx),
47
+ })
48
+ return response
49
+ }
@@ -0,0 +1,41 @@
1
+ export type EcommerceSubscriberContext = {
2
+ resolve: <T = unknown>(name: string) => T
3
+ eventName?: string
4
+ tenantId?: string | null
5
+ organizationId?: string | null
6
+ }
7
+
8
+ export type EventScope = { tenantId: string | null; organizationId: string | null }
9
+
10
+ export function asPayloadRecord(payload: unknown): Record<string, unknown> {
11
+ return typeof payload === 'object' && payload !== null && !Array.isArray(payload)
12
+ ? (payload as Record<string, unknown>)
13
+ : {}
14
+ }
15
+
16
+ export function readPayloadString(payload: Record<string, unknown>, key: string): string | null {
17
+ const value = payload[key]
18
+ return typeof value === 'string' && value.length > 0 ? value : null
19
+ }
20
+
21
+ /** Trusted emitter scope first (events AGENTS.md), payload fields only as a fallback. */
22
+ export function readEventScope(payload: Record<string, unknown>, ctx: EcommerceSubscriberContext): EventScope {
23
+ return {
24
+ tenantId: ctx.tenantId ?? readPayloadString(payload, 'tenantId'),
25
+ organizationId: ctx.organizationId ?? readPayloadString(payload, 'organizationId'),
26
+ }
27
+ }
28
+
29
+ export function eventAction(eventName: string | undefined): string | null {
30
+ if (!eventName) return null
31
+ const segments = eventName.split('.')
32
+ return segments.length > 0 ? segments[segments.length - 1] : null
33
+ }
34
+
35
+ export function tryResolveService<T>(ctx: Pick<EcommerceSubscriberContext, 'resolve'>, name: string): T | null {
36
+ try {
37
+ return ctx.resolve<T | null | undefined>(name) ?? null
38
+ } catch {
39
+ return null
40
+ }
41
+ }
@@ -0,0 +1,69 @@
1
+ import type { EffectiveAssortmentScope } from '@open-mercato/shared/lib/catalog-visibility'
2
+ import type { EcommercePriceDisplayMode, EcommercePriceSortFallback, EcommerceStoreSettings } from '../data/validators'
3
+
4
+ /**
5
+ * Suite-wide buyer shape (SPEC-029 v4.6 §6, §6.0). Identity ids are read fresh from
6
+ * `customer_accounts.CustomerUser`, never from JWT claims.
7
+ */
8
+ export type BuyerContext = {
9
+ /** `customer_accounts.CustomerUser.id` (auth.sub); null for anonymous buyers. */
10
+ customerUserId: string | null
11
+ /** `personEntityId ?? customerEntityId` — the most specific identity ("person wins"). */
12
+ customerId: string | null
13
+ /** `CustomerUser.customer_entity_id` — always the company. */
14
+ companyId: string | null
15
+ /** Distinct non-null `[personEntityId, customerEntityId]`, person first (D3). */
16
+ customerIds: string[]
17
+ /** Priority-ordered union over `customerIds` from `customerGroupsService`. */
18
+ customerGroupIds: string[]
19
+ isAuthenticated: boolean
20
+ /** Derived from the resolved price kind's `displayMode` (§6.1a); never set independently. */
21
+ taxMode: EcommercePriceDisplayMode
22
+ /** Group terms override the channel default; passed to `PricingContext` as a filter (D2). */
23
+ priceKindId: string | null
24
+ allowPurchaseOnAccount: boolean
25
+ approvalRequiredAbove: number | null
26
+ /** `intersectScopes(channel, group union)`; null = unrestricted, [] = deny-all. */
27
+ assortmentScope: EffectiveAssortmentScope
28
+ /** `hashEffectiveScope(assortmentScope)` — digest of the canonicalized resolved scope, never of its inputs. */
29
+ assortmentScopeHash: string
30
+ /** Truncated sha256 of channelId, currencyCode, priceKindId and sorted customerGroupIds. */
31
+ priceScopeKey: string
32
+ /** Null when no id in `customerIds` has its own price rows; otherwise those ids, person first, joined with ','. */
33
+ customerOverlayId: string | null
34
+ }
35
+
36
+ export type StoreContextStore = {
37
+ id: string
38
+ code: string
39
+ name: string
40
+ slug: string
41
+ status: 'active'
42
+ defaultLocale: string
43
+ supportedLocales: string[]
44
+ defaultCurrencyCode: string
45
+ settings: EcommerceStoreSettings
46
+ }
47
+
48
+ export type StoreContextChannel = {
49
+ channelBindingId: string
50
+ salesChannelId: string
51
+ priceKindId: string | null
52
+ priceSortFallback: EcommercePriceSortFallback
53
+ }
54
+
55
+ export type StoreContext = {
56
+ store: StoreContextStore
57
+ tenantId: string
58
+ organizationId: string
59
+ channel: StoreContextChannel | null
60
+ buyer: BuyerContext
61
+ effectiveLocale: string
62
+ requestedLocale: string | null
63
+ currencyCode: string
64
+ /**
65
+ * Stable digest of every field that can change what a buyer sees or pays (§6.1).
66
+ * MUST be a component of every buyer-dependent cache key derived from this context.
67
+ */
68
+ digest: string
69
+ }