@open-mercato/core 0.9.1-develop.7474.1.51db10479d → 0.9.1-develop.7475.1.28ca93ed97

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (597) hide show
  1. package/.turbo/turbo-build.log +2 -2
  2. package/dist/generated/entities/catalog_price_history_entry/index.js +53 -0
  3. package/dist/generated/entities/catalog_price_history_entry/index.js.map +7 -0
  4. package/dist/generated/entities/customer_group_terms/index.js +2 -0
  5. package/dist/generated/entities/customer_group_terms/index.js.map +2 -2
  6. package/dist/generated/entities/ecommerce_store/index.js +33 -0
  7. package/dist/generated/entities/ecommerce_store/index.js.map +7 -0
  8. package/dist/generated/entities/ecommerce_store_channel_binding/index.js +29 -0
  9. package/dist/generated/entities/ecommerce_store_channel_binding/index.js.map +7 -0
  10. package/dist/generated/entities/ecommerce_store_domain_binding/index.js +23 -0
  11. package/dist/generated/entities/ecommerce_store_domain_binding/index.js.map +7 -0
  12. package/dist/generated/entities.ids.generated.js +8 -1
  13. package/dist/generated/entities.ids.generated.js.map +2 -2
  14. package/dist/generated/entity-fields-registry.js +72 -0
  15. package/dist/generated/entity-fields-registry.js.map +2 -2
  16. package/dist/helpers/integration/customerGroupsFixtures.js.map +2 -2
  17. package/dist/modules/catalog/acl.js +8 -1
  18. package/dist/modules/catalog/acl.js.map +2 -2
  19. package/dist/modules/catalog/api/config/omnibus/route.js +184 -0
  20. package/dist/modules/catalog/api/config/omnibus/route.js.map +7 -0
  21. package/dist/modules/catalog/api/prices/history/route.js +131 -0
  22. package/dist/modules/catalog/api/prices/history/route.js.map +7 -0
  23. package/dist/modules/catalog/api/prices/omnibus-preview/route.js +184 -0
  24. package/dist/modules/catalog/api/prices/omnibus-preview/route.js.map +7 -0
  25. package/dist/modules/catalog/api/products/route.js +54 -245
  26. package/dist/modules/catalog/api/products/route.js.map +3 -3
  27. package/dist/modules/catalog/api/tags/route.js +6 -0
  28. package/dist/modules/catalog/api/tags/route.js.map +2 -2
  29. package/dist/modules/catalog/backend/config/catalog/page.js +3 -1
  30. package/dist/modules/catalog/backend/config/catalog/page.js.map +2 -2
  31. package/dist/modules/catalog/cli.js +80 -2
  32. package/dist/modules/catalog/cli.js.map +2 -2
  33. package/dist/modules/catalog/commands/categories.js +51 -2
  34. package/dist/modules/catalog/commands/categories.js.map +2 -2
  35. package/dist/modules/catalog/commands/prices.js +43 -1
  36. package/dist/modules/catalog/commands/prices.js.map +2 -2
  37. package/dist/modules/catalog/commands/products.js +71 -0
  38. package/dist/modules/catalog/commands/products.js.map +2 -2
  39. package/dist/modules/catalog/commands/variants.js +7 -0
  40. package/dist/modules/catalog/commands/variants.js.map +2 -2
  41. package/dist/modules/catalog/components/OmnibusSettings.js +703 -0
  42. package/dist/modules/catalog/components/OmnibusSettings.js.map +7 -0
  43. package/dist/modules/catalog/components/PriceEditorOmnibusRow.js +152 -0
  44. package/dist/modules/catalog/components/PriceEditorOmnibusRow.js.map +7 -0
  45. package/dist/modules/catalog/components/lookupSources.js +31 -0
  46. package/dist/modules/catalog/components/lookupSources.js.map +7 -0
  47. package/dist/modules/catalog/components/prices/PriceScopeSelectors.js +8 -2
  48. package/dist/modules/catalog/components/prices/PriceScopeSelectors.js.map +2 -2
  49. package/dist/modules/catalog/components/products/VariantBuilder.js +11 -1
  50. package/dist/modules/catalog/components/products/VariantBuilder.js.map +2 -2
  51. package/dist/modules/catalog/data/entities.js +122 -1
  52. package/dist/modules/catalog/data/entities.js.map +2 -2
  53. package/dist/modules/catalog/data/validators.js +78 -0
  54. package/dist/modules/catalog/data/validators.js.map +2 -2
  55. package/dist/modules/catalog/di.js +18 -0
  56. package/dist/modules/catalog/di.js.map +2 -2
  57. package/dist/modules/catalog/lib/omnibus.js +285 -0
  58. package/dist/modules/catalog/lib/omnibus.js.map +7 -0
  59. package/dist/modules/catalog/lib/omnibusBackfill.js +146 -0
  60. package/dist/modules/catalog/lib/omnibusBackfill.js.map +7 -0
  61. package/dist/modules/catalog/lib/omnibusCache.js +89 -0
  62. package/dist/modules/catalog/lib/omnibusCache.js.map +7 -0
  63. package/dist/modules/catalog/lib/omnibusConfig.js +64 -0
  64. package/dist/modules/catalog/lib/omnibusConfig.js.map +7 -0
  65. package/dist/modules/catalog/lib/omnibusHistoryQueries.js +191 -0
  66. package/dist/modules/catalog/lib/omnibusHistoryQueries.js.map +7 -0
  67. package/dist/modules/catalog/lib/omnibusPresentedEntry.js +73 -0
  68. package/dist/modules/catalog/lib/omnibusPresentedEntry.js.map +7 -0
  69. package/dist/modules/catalog/lib/omnibusProductListEnrichment.js +94 -0
  70. package/dist/modules/catalog/lib/omnibusProductListEnrichment.js.map +7 -0
  71. package/dist/modules/catalog/lib/omnibusTypes.js +115 -0
  72. package/dist/modules/catalog/lib/omnibusTypes.js.map +7 -0
  73. package/dist/modules/catalog/lib/optionSchemaTranslations.js +55 -0
  74. package/dist/modules/catalog/lib/optionSchemaTranslations.js.map +7 -0
  75. package/dist/modules/catalog/lib/priceHistoryQuery.js +103 -0
  76. package/dist/modules/catalog/lib/priceHistoryQuery.js.map +7 -0
  77. package/dist/modules/catalog/lib/pricing.js +26 -2
  78. package/dist/modules/catalog/lib/pricing.js.map +2 -2
  79. package/dist/modules/catalog/lib/productFilters.js +273 -0
  80. package/dist/modules/catalog/lib/productFilters.js.map +7 -0
  81. package/dist/modules/catalog/lib/productScopeKeys.js +105 -0
  82. package/dist/modules/catalog/lib/productScopeKeys.js.map +7 -0
  83. package/dist/modules/catalog/lib/productScopeReindex.js +73 -0
  84. package/dist/modules/catalog/lib/productScopeReindex.js.map +7 -0
  85. package/dist/modules/catalog/migrations/Migration20261005105352_catalog.js +39 -0
  86. package/dist/modules/catalog/migrations/Migration20261005105352_catalog.js.map +7 -0
  87. package/dist/modules/catalog/migrations/Migration20261005150824_catalog.js +39 -0
  88. package/dist/modules/catalog/migrations/Migration20261005150824_catalog.js.map +7 -0
  89. package/dist/modules/catalog/services/catalogOmnibusService.js +480 -0
  90. package/dist/modules/catalog/services/catalogOmnibusService.js.map +7 -0
  91. package/dist/modules/catalog/setup.js +3 -1
  92. package/dist/modules/catalog/setup.js.map +2 -2
  93. package/dist/modules/catalog/subscribers/product-scope-keys-category-deleted.js +16 -0
  94. package/dist/modules/catalog/subscribers/product-scope-keys-category-deleted.js.map +7 -0
  95. package/dist/modules/catalog/subscribers/product-scope-keys-category-updated.js +17 -0
  96. package/dist/modules/catalog/subscribers/product-scope-keys-category-updated.js.map +7 -0
  97. package/dist/modules/catalog/translations.js +10 -0
  98. package/dist/modules/catalog/translations.js.map +2 -2
  99. package/dist/modules/configs/lib/upgrade-actions.js +28 -0
  100. package/dist/modules/configs/lib/upgrade-actions.js.map +2 -2
  101. package/dist/modules/customer_groups/api/customer-groups/[id]/terms/route.js +5 -0
  102. package/dist/modules/customer_groups/api/customer-groups/[id]/terms/route.js.map +2 -2
  103. package/dist/modules/customer_groups/components/CustomerGroupTermsSection.js +111 -3
  104. package/dist/modules/customer_groups/components/CustomerGroupTermsSection.js.map +2 -2
  105. package/dist/modules/customer_groups/components/customerGroupAssortmentScope.js +43 -0
  106. package/dist/modules/customer_groups/components/customerGroupAssortmentScope.js.map +7 -0
  107. package/dist/modules/customer_groups/data/entities.js +3 -0
  108. package/dist/modules/customer_groups/data/entities.js.map +2 -2
  109. package/dist/modules/customer_groups/data/validators.js +9 -0
  110. package/dist/modules/customer_groups/data/validators.js.map +2 -2
  111. package/dist/modules/customer_groups/migrations/Migration20261005110250_customer_groups.js +17 -0
  112. package/dist/modules/customer_groups/migrations/Migration20261005110250_customer_groups.js.map +7 -0
  113. package/dist/modules/customer_groups/services/customerGroupsService.js +62 -15
  114. package/dist/modules/customer_groups/services/customerGroupsService.js.map +2 -2
  115. package/dist/modules/ecommerce/acl.js +13 -0
  116. package/dist/modules/ecommerce/acl.js.map +7 -0
  117. package/dist/modules/ecommerce/api/domain-mappings/route.js +72 -0
  118. package/dist/modules/ecommerce/api/domain-mappings/route.js.map +7 -0
  119. package/dist/modules/ecommerce/api/openapi.js +27 -0
  120. package/dist/modules/ecommerce/api/openapi.js.map +7 -0
  121. package/dist/modules/ecommerce/api/store-channel-bindings/[id]/assortment-count/route.js +109 -0
  122. package/dist/modules/ecommerce/api/store-channel-bindings/[id]/assortment-count/route.js.map +7 -0
  123. package/dist/modules/ecommerce/api/store-channel-bindings/crud.js +265 -0
  124. package/dist/modules/ecommerce/api/store-channel-bindings/crud.js.map +7 -0
  125. package/dist/modules/ecommerce/api/store-channel-bindings/route.js +67 -0
  126. package/dist/modules/ecommerce/api/store-channel-bindings/route.js.map +7 -0
  127. package/dist/modules/ecommerce/api/store-domain-bindings/crud.js +296 -0
  128. package/dist/modules/ecommerce/api/store-domain-bindings/crud.js.map +7 -0
  129. package/dist/modules/ecommerce/api/store-domain-bindings/route.js +73 -0
  130. package/dist/modules/ecommerce/api/store-domain-bindings/route.js.map +7 -0
  131. package/dist/modules/ecommerce/api/storefront/categories/[slug]/route.js +87 -0
  132. package/dist/modules/ecommerce/api/storefront/categories/[slug]/route.js.map +7 -0
  133. package/dist/modules/ecommerce/api/storefront/categories/openapiSchemas.js +50 -0
  134. package/dist/modules/ecommerce/api/storefront/categories/openapiSchemas.js.map +7 -0
  135. package/dist/modules/ecommerce/api/storefront/categories/route.js +75 -0
  136. package/dist/modules/ecommerce/api/storefront/categories/route.js.map +7 -0
  137. package/dist/modules/ecommerce/api/storefront/context/route.js +163 -0
  138. package/dist/modules/ecommerce/api/storefront/context/route.js.map +7 -0
  139. package/dist/modules/ecommerce/api/storefront/products/[idOrHandle]/route.js +87 -0
  140. package/dist/modules/ecommerce/api/storefront/products/[idOrHandle]/route.js.map +7 -0
  141. package/dist/modules/ecommerce/api/storefront/products/openapiSchemas.js +177 -0
  142. package/dist/modules/ecommerce/api/storefront/products/openapiSchemas.js.map +7 -0
  143. package/dist/modules/ecommerce/api/storefront/products/route.js +78 -0
  144. package/dist/modules/ecommerce/api/storefront/products/route.js.map +7 -0
  145. package/dist/modules/ecommerce/api/storefront/search/suggest/openapiSchemas.js +25 -0
  146. package/dist/modules/ecommerce/api/storefront/search/suggest/openapiSchemas.js.map +7 -0
  147. package/dist/modules/ecommerce/api/storefront/search/suggest/route.js +75 -0
  148. package/dist/modules/ecommerce/api/storefront/search/suggest/route.js.map +7 -0
  149. package/dist/modules/ecommerce/api/storefront/storefrontRouteSupport.js +58 -0
  150. package/dist/modules/ecommerce/api/storefront/storefrontRouteSupport.js.map +7 -0
  151. package/dist/modules/ecommerce/api/stores/[id]/branding/route.js +183 -0
  152. package/dist/modules/ecommerce/api/stores/[id]/branding/route.js.map +7 -0
  153. package/dist/modules/ecommerce/api/stores/[id]/preview-branding/route.js +92 -0
  154. package/dist/modules/ecommerce/api/stores/[id]/preview-branding/route.js.map +7 -0
  155. package/dist/modules/ecommerce/api/stores/crud.js +401 -0
  156. package/dist/modules/ecommerce/api/stores/crud.js.map +7 -0
  157. package/dist/modules/ecommerce/api/stores/route.js +64 -0
  158. package/dist/modules/ecommerce/api/stores/route.js.map +7 -0
  159. package/dist/modules/ecommerce/backend/config/ecommerce/[id]/page.js +9 -0
  160. package/dist/modules/ecommerce/backend/config/ecommerce/[id]/page.js.map +7 -0
  161. package/dist/modules/ecommerce/backend/config/ecommerce/[id]/page.meta.js +16 -0
  162. package/dist/modules/ecommerce/backend/config/ecommerce/[id]/page.meta.js.map +7 -0
  163. package/dist/modules/ecommerce/backend/config/ecommerce/page.js +9 -0
  164. package/dist/modules/ecommerce/backend/config/ecommerce/page.js.map +7 -0
  165. package/dist/modules/ecommerce/backend/config/ecommerce/page.meta.js +16 -0
  166. package/dist/modules/ecommerce/backend/config/ecommerce/page.meta.js.map +7 -0
  167. package/dist/modules/ecommerce/commands/stores.js +110 -0
  168. package/dist/modules/ecommerce/commands/stores.js.map +7 -0
  169. package/dist/modules/ecommerce/components/ChannelAssortmentCount.js +90 -0
  170. package/dist/modules/ecommerce/components/ChannelAssortmentCount.js.map +7 -0
  171. package/dist/modules/ecommerce/components/ChannelBindingFormSections.js +33 -0
  172. package/dist/modules/ecommerce/components/ChannelBindingFormSections.js.map +7 -0
  173. package/dist/modules/ecommerce/components/ChannelLookupPicker.js +11 -0
  174. package/dist/modules/ecommerce/components/ChannelLookupPicker.js.map +7 -0
  175. package/dist/modules/ecommerce/components/DomainStatusWarning.js +24 -0
  176. package/dist/modules/ecommerce/components/DomainStatusWarning.js.map +7 -0
  177. package/dist/modules/ecommerce/components/StoreAvailabilityDefaultsSection.js +190 -0
  178. package/dist/modules/ecommerce/components/StoreAvailabilityDefaultsSection.js.map +7 -0
  179. package/dist/modules/ecommerce/components/StoreBrandingControls.js +67 -0
  180. package/dist/modules/ecommerce/components/StoreBrandingControls.js.map +7 -0
  181. package/dist/modules/ecommerce/components/StoreBrandingPreview.js +69 -0
  182. package/dist/modules/ecommerce/components/StoreBrandingPreview.js.map +7 -0
  183. package/dist/modules/ecommerce/components/StoreBrandingTab.js +218 -0
  184. package/dist/modules/ecommerce/components/StoreBrandingTab.js.map +7 -0
  185. package/dist/modules/ecommerce/components/StoreChannelBindingDialog.js +270 -0
  186. package/dist/modules/ecommerce/components/StoreChannelBindingDialog.js.map +7 -0
  187. package/dist/modules/ecommerce/components/StoreChannelsTab.js +313 -0
  188. package/dist/modules/ecommerce/components/StoreChannelsTab.js.map +7 -0
  189. package/dist/modules/ecommerce/components/StoreCreateDialog.js +164 -0
  190. package/dist/modules/ecommerce/components/StoreCreateDialog.js.map +7 -0
  191. package/dist/modules/ecommerce/components/StoreDomainBindingDialog.js +162 -0
  192. package/dist/modules/ecommerce/components/StoreDomainBindingDialog.js.map +7 -0
  193. package/dist/modules/ecommerce/components/StoreDomainsTab.js +357 -0
  194. package/dist/modules/ecommerce/components/StoreDomainsTab.js.map +7 -0
  195. package/dist/modules/ecommerce/components/StoreEditPage.js +155 -0
  196. package/dist/modules/ecommerce/components/StoreEditPage.js.map +7 -0
  197. package/dist/modules/ecommerce/components/StoreGeneralTab.js +167 -0
  198. package/dist/modules/ecommerce/components/StoreGeneralTab.js.map +7 -0
  199. package/dist/modules/ecommerce/components/StoreListPage.js +290 -0
  200. package/dist/modules/ecommerce/components/StoreListPage.js.map +7 -0
  201. package/dist/modules/ecommerce/components/StoreSeoTab.js +132 -0
  202. package/dist/modules/ecommerce/components/StoreSeoTab.js.map +7 -0
  203. package/dist/modules/ecommerce/components/StoreStatusBadge.js +20 -0
  204. package/dist/modules/ecommerce/components/StoreStatusBadge.js.map +7 -0
  205. package/dist/modules/ecommerce/components/channelLookups.js +28 -0
  206. package/dist/modules/ecommerce/components/channelLookups.js.map +7 -0
  207. package/dist/modules/ecommerce/components/storeAdmin.js +41 -0
  208. package/dist/modules/ecommerce/components/storeAdmin.js.map +7 -0
  209. package/dist/modules/ecommerce/components/storeBrandingForm.js +197 -0
  210. package/dist/modules/ecommerce/components/storeBrandingForm.js.map +7 -0
  211. package/dist/modules/ecommerce/components/storeChannels.js +140 -0
  212. package/dist/modules/ecommerce/components/storeChannels.js.map +7 -0
  213. package/dist/modules/ecommerce/components/storeDomains.js +167 -0
  214. package/dist/modules/ecommerce/components/storeDomains.js.map +7 -0
  215. package/dist/modules/ecommerce/components/storeEditTabs.js +62 -0
  216. package/dist/modules/ecommerce/components/storeEditTabs.js.map +7 -0
  217. package/dist/modules/ecommerce/components/storeGeneral.js +95 -0
  218. package/dist/modules/ecommerce/components/storeGeneral.js.map +7 -0
  219. package/dist/modules/ecommerce/components/storeSeo.js +69 -0
  220. package/dist/modules/ecommerce/components/storeSeo.js.map +7 -0
  221. package/dist/modules/ecommerce/components/useChannelAssortmentCount.js +45 -0
  222. package/dist/modules/ecommerce/components/useChannelAssortmentCount.js.map +7 -0
  223. package/dist/modules/ecommerce/components/useStoreAccess.js +28 -0
  224. package/dist/modules/ecommerce/components/useStoreAccess.js.map +7 -0
  225. package/dist/modules/ecommerce/data/enrichers.js +157 -0
  226. package/dist/modules/ecommerce/data/enrichers.js.map +7 -0
  227. package/dist/modules/ecommerce/data/entities.js +197 -0
  228. package/dist/modules/ecommerce/data/entities.js.map +7 -0
  229. package/dist/modules/ecommerce/data/validators.js +406 -0
  230. package/dist/modules/ecommerce/data/validators.js.map +7 -0
  231. package/dist/modules/ecommerce/di.js +11 -0
  232. package/dist/modules/ecommerce/di.js.map +7 -0
  233. package/dist/modules/ecommerce/events.js +27 -0
  234. package/dist/modules/ecommerce/events.js.map +7 -0
  235. package/dist/modules/ecommerce/index.js +16 -0
  236. package/dist/modules/ecommerce/index.js.map +7 -0
  237. package/dist/modules/ecommerce/lib/assortmentCount.js +112 -0
  238. package/dist/modules/ecommerce/lib/assortmentCount.js.map +7 -0
  239. package/dist/modules/ecommerce/lib/brandingStyles.js +184 -0
  240. package/dist/modules/ecommerce/lib/brandingStyles.js.map +7 -0
  241. package/dist/modules/ecommerce/lib/buyerContext.js +284 -0
  242. package/dist/modules/ecommerce/lib/buyerContext.js.map +7 -0
  243. package/dist/modules/ecommerce/lib/cacheInvalidation.js +186 -0
  244. package/dist/modules/ecommerce/lib/cacheInvalidation.js.map +7 -0
  245. package/dist/modules/ecommerce/lib/cacheKeys.js +236 -0
  246. package/dist/modules/ecommerce/lib/cacheKeys.js.map +7 -0
  247. package/dist/modules/ecommerce/lib/createConflictRecheck.js +29 -0
  248. package/dist/modules/ecommerce/lib/createConflictRecheck.js.map +7 -0
  249. package/dist/modules/ecommerce/lib/crudEvents.js +94 -0
  250. package/dist/modules/ecommerce/lib/crudEvents.js.map +7 -0
  251. package/dist/modules/ecommerce/lib/crudSupport.js +64 -0
  252. package/dist/modules/ecommerce/lib/crudSupport.js.map +7 -0
  253. package/dist/modules/ecommerce/lib/domainBindingRebind.js +99 -0
  254. package/dist/modules/ecommerce/lib/domainBindingRebind.js.map +7 -0
  255. package/dist/modules/ecommerce/lib/domainMappingSummaries.js +39 -0
  256. package/dist/modules/ecommerce/lib/domainMappingSummaries.js.map +7 -0
  257. package/dist/modules/ecommerce/lib/exclusiveFlag.js +57 -0
  258. package/dist/modules/ecommerce/lib/exclusiveFlag.js.map +7 -0
  259. package/dist/modules/ecommerce/lib/references.js +70 -0
  260. package/dist/modules/ecommerce/lib/references.js.map +7 -0
  261. package/dist/modules/ecommerce/lib/seedDraftStore.js +99 -0
  262. package/dist/modules/ecommerce/lib/seedDraftStore.js.map +7 -0
  263. package/dist/modules/ecommerce/lib/storeBindingCascade.js +109 -0
  264. package/dist/modules/ecommerce/lib/storeBindingCascade.js.map +7 -0
  265. package/dist/modules/ecommerce/lib/storeBranding.js +40 -0
  266. package/dist/modules/ecommerce/lib/storeBranding.js.map +7 -0
  267. package/dist/modules/ecommerce/lib/storeBrandingRoute.js +76 -0
  268. package/dist/modules/ecommerce/lib/storeBrandingRoute.js.map +7 -0
  269. package/dist/modules/ecommerce/lib/storeContext.js +393 -0
  270. package/dist/modules/ecommerce/lib/storeContext.js.map +7 -0
  271. package/dist/modules/ecommerce/lib/storeContextService.js +43 -0
  272. package/dist/modules/ecommerce/lib/storeContextService.js.map +7 -0
  273. package/dist/modules/ecommerce/lib/storeLocale.js +60 -0
  274. package/dist/modules/ecommerce/lib/storeLocale.js.map +7 -0
  275. package/dist/modules/ecommerce/lib/storeNotifications.js +41 -0
  276. package/dist/modules/ecommerce/lib/storeNotifications.js.map +7 -0
  277. package/dist/modules/ecommerce/lib/storefrontCatalogSupport.js +248 -0
  278. package/dist/modules/ecommerce/lib/storefrontCatalogSupport.js.map +7 -0
  279. package/dist/modules/ecommerce/lib/storefrontCategories.js +241 -0
  280. package/dist/modules/ecommerce/lib/storefrontCategories.js.map +7 -0
  281. package/dist/modules/ecommerce/lib/storefrontCategoryCache.js +71 -0
  282. package/dist/modules/ecommerce/lib/storefrontCategoryCache.js.map +7 -0
  283. package/dist/modules/ecommerce/lib/storefrontDetail.js +447 -0
  284. package/dist/modules/ecommerce/lib/storefrontDetail.js.map +7 -0
  285. package/dist/modules/ecommerce/lib/storefrontFacets.js +441 -0
  286. package/dist/modules/ecommerce/lib/storefrontFacets.js.map +7 -0
  287. package/dist/modules/ecommerce/lib/storefrontOptionFilter.js +15 -0
  288. package/dist/modules/ecommerce/lib/storefrontOptionFilter.js.map +7 -0
  289. package/dist/modules/ecommerce/lib/storefrontPricing.js +393 -0
  290. package/dist/modules/ecommerce/lib/storefrontPricing.js.map +7 -0
  291. package/dist/modules/ecommerce/lib/storefrontProductCache.js +90 -0
  292. package/dist/modules/ecommerce/lib/storefrontProductCache.js.map +7 -0
  293. package/dist/modules/ecommerce/lib/storefrontProductScope.js +107 -0
  294. package/dist/modules/ecommerce/lib/storefrontProductScope.js.map +7 -0
  295. package/dist/modules/ecommerce/lib/storefrontProducts.js +670 -0
  296. package/dist/modules/ecommerce/lib/storefrontProducts.js.map +7 -0
  297. package/dist/modules/ecommerce/lib/storefrontQuery.js +156 -0
  298. package/dist/modules/ecommerce/lib/storefrontQuery.js.map +7 -0
  299. package/dist/modules/ecommerce/lib/storefrontRateLimit.js +99 -0
  300. package/dist/modules/ecommerce/lib/storefrontRateLimit.js.map +7 -0
  301. package/dist/modules/ecommerce/lib/storefrontSearch.js +303 -0
  302. package/dist/modules/ecommerce/lib/storefrontSearch.js.map +7 -0
  303. package/dist/modules/ecommerce/lib/storefrontSearchCache.js +35 -0
  304. package/dist/modules/ecommerce/lib/storefrontSearchCache.js.map +7 -0
  305. package/dist/modules/ecommerce/lib/subscriberSupport.js +33 -0
  306. package/dist/modules/ecommerce/lib/subscriberSupport.js.map +7 -0
  307. package/dist/modules/ecommerce/lib/types.js +1 -0
  308. package/dist/modules/ecommerce/lib/types.js.map +7 -0
  309. package/dist/modules/ecommerce/migrations/Migration20261005113233_ecommerce.js +32 -0
  310. package/dist/modules/ecommerce/migrations/Migration20261005113233_ecommerce.js.map +7 -0
  311. package/dist/modules/ecommerce/migrations/Migration20261006112733_ecommerce.js +17 -0
  312. package/dist/modules/ecommerce/migrations/Migration20261006112733_ecommerce.js.map +7 -0
  313. package/dist/modules/ecommerce/notifications.client.js +47 -0
  314. package/dist/modules/ecommerce/notifications.client.js.map +7 -0
  315. package/dist/modules/ecommerce/notifications.js +48 -0
  316. package/dist/modules/ecommerce/notifications.js.map +7 -0
  317. package/dist/modules/ecommerce/search.js +83 -0
  318. package/dist/modules/ecommerce/search.js.map +7 -0
  319. package/dist/modules/ecommerce/setup.js +17 -0
  320. package/dist/modules/ecommerce/setup.js.map +7 -0
  321. package/dist/modules/ecommerce/subscribers/assortment-empty-notifications.js +14 -0
  322. package/dist/modules/ecommerce/subscribers/assortment-empty-notifications.js.map +7 -0
  323. package/dist/modules/ecommerce/subscribers/availability-policy-cache-invalidation.js +14 -0
  324. package/dist/modules/ecommerce/subscribers/availability-policy-cache-invalidation.js.map +7 -0
  325. package/dist/modules/ecommerce/subscribers/catalog-category-cache-invalidation.js +14 -0
  326. package/dist/modules/ecommerce/subscribers/catalog-category-cache-invalidation.js.map +7 -0
  327. package/dist/modules/ecommerce/subscribers/catalog-price-cache-invalidation.js +14 -0
  328. package/dist/modules/ecommerce/subscribers/catalog-price-cache-invalidation.js.map +7 -0
  329. package/dist/modules/ecommerce/subscribers/catalog-product-cache-invalidation.js +14 -0
  330. package/dist/modules/ecommerce/subscribers/catalog-product-cache-invalidation.js.map +7 -0
  331. package/dist/modules/ecommerce/subscribers/catalog-variant-cache-invalidation.js +14 -0
  332. package/dist/modules/ecommerce/subscribers/catalog-variant-cache-invalidation.js.map +7 -0
  333. package/dist/modules/ecommerce/subscribers/customer-group-cache-invalidation.js +14 -0
  334. package/dist/modules/ecommerce/subscribers/customer-group-cache-invalidation.js.map +7 -0
  335. package/dist/modules/ecommerce/subscribers/customer-group-membership-cache-invalidation.js +14 -0
  336. package/dist/modules/ecommerce/subscribers/customer-group-membership-cache-invalidation.js.map +7 -0
  337. package/dist/modules/ecommerce/subscribers/customer-group-terms-cache-invalidation.js +14 -0
  338. package/dist/modules/ecommerce/subscribers/customer-group-terms-cache-invalidation.js.map +7 -0
  339. package/dist/modules/ecommerce/subscribers/customer-user-deleted-cache-invalidation.js +14 -0
  340. package/dist/modules/ecommerce/subscribers/customer-user-deleted-cache-invalidation.js.map +7 -0
  341. package/dist/modules/ecommerce/subscribers/customer-user-updated-cache-invalidation.js +14 -0
  342. package/dist/modules/ecommerce/subscribers/customer-user-updated-cache-invalidation.js.map +7 -0
  343. package/dist/modules/ecommerce/subscribers/domain-binding-rebind.js +16 -0
  344. package/dist/modules/ecommerce/subscribers/domain-binding-rebind.js.map +7 -0
  345. package/dist/modules/ecommerce/subscribers/domain-mapping-cache-invalidation.js +14 -0
  346. package/dist/modules/ecommerce/subscribers/domain-mapping-cache-invalidation.js.map +7 -0
  347. package/dist/modules/ecommerce/subscribers/store-cache-invalidation.js +14 -0
  348. package/dist/modules/ecommerce/subscribers/store-cache-invalidation.js.map +7 -0
  349. package/dist/modules/ecommerce/subscribers/store-channel-binding-cache-invalidation.js +14 -0
  350. package/dist/modules/ecommerce/subscribers/store-channel-binding-cache-invalidation.js.map +7 -0
  351. package/dist/modules/ecommerce/subscribers/store-domain-binding-cache-invalidation.js +14 -0
  352. package/dist/modules/ecommerce/subscribers/store-domain-binding-cache-invalidation.js.map +7 -0
  353. package/dist/modules/ecommerce/subscribers/store-notifications.js +14 -0
  354. package/dist/modules/ecommerce/subscribers/store-notifications.js.map +7 -0
  355. package/dist/modules/query_index/lib/batch.js +23 -9
  356. package/dist/modules/query_index/lib/batch.js.map +2 -2
  357. package/dist/modules/query_index/lib/doc-enrichers.js +212 -0
  358. package/dist/modules/query_index/lib/doc-enrichers.js.map +7 -0
  359. package/dist/modules/query_index/lib/document.js +2 -0
  360. package/dist/modules/query_index/lib/document.js.map +2 -2
  361. package/dist/modules/query_index/lib/engine.js +68 -1
  362. package/dist/modules/query_index/lib/engine.js.map +2 -2
  363. package/dist/modules/query_index/lib/indexer.js +7 -0
  364. package/dist/modules/query_index/lib/indexer.js.map +2 -2
  365. package/dist/modules/query_index/lib/search-tokens.js +2 -0
  366. package/dist/modules/query_index/lib/search-tokens.js.map +2 -2
  367. package/dist/modules/query_index/migrations/Migration20261005201500_query_index.js +17 -0
  368. package/dist/modules/query_index/migrations/Migration20261005201500_query_index.js.map +7 -0
  369. package/dist/modules/translations/components/TranslationManager.js +3 -3
  370. package/dist/modules/translations/components/TranslationManager.js.map +2 -2
  371. package/dist/modules/translations/data/validators.js +1 -1
  372. package/dist/modules/translations/data/validators.js.map +1 -1
  373. package/dist/modules/translations/lib/batch.js +21 -1
  374. package/dist/modules/translations/lib/batch.js.map +2 -2
  375. package/dist/modules/translations/lib/helpers.js +3 -0
  376. package/dist/modules/translations/lib/helpers.js.map +2 -2
  377. package/dist/modules/translations/lib/resolve-field-list.js +14 -2
  378. package/dist/modules/translations/lib/resolve-field-list.js.map +2 -2
  379. package/generated/entities/catalog_price_history_entry/index.ts +25 -0
  380. package/generated/entities/customer_group_terms/index.ts +1 -0
  381. package/generated/entities/ecommerce_store/index.ts +15 -0
  382. package/generated/entities/ecommerce_store_channel_binding/index.ts +13 -0
  383. package/generated/entities/ecommerce_store_domain_binding/index.ts +10 -0
  384. package/generated/entities.ids.generated.ts +8 -1
  385. package/generated/entity-fields-registry.ts +72 -0
  386. package/package.json +7 -7
  387. package/src/helpers/integration/customerGroupsFixtures.ts +2 -0
  388. package/src/modules/auth/i18n/de.json +8 -0
  389. package/src/modules/auth/i18n/en.json +8 -0
  390. package/src/modules/auth/i18n/es.json +8 -0
  391. package/src/modules/auth/i18n/ko.json +8 -0
  392. package/src/modules/auth/i18n/pl.json +8 -0
  393. package/src/modules/catalog/AGENTS.md +5 -2
  394. package/src/modules/catalog/acl.ts +7 -0
  395. package/src/modules/catalog/api/config/omnibus/route.ts +204 -0
  396. package/src/modules/catalog/api/prices/history/route.ts +139 -0
  397. package/src/modules/catalog/api/prices/omnibus-preview/route.ts +202 -0
  398. package/src/modules/catalog/api/products/route.ts +53 -308
  399. package/src/modules/catalog/api/tags/route.ts +6 -0
  400. package/src/modules/catalog/backend/config/catalog/page.tsx +2 -0
  401. package/src/modules/catalog/cli.ts +84 -1
  402. package/src/modules/catalog/commands/categories.ts +54 -2
  403. package/src/modules/catalog/commands/prices.ts +43 -1
  404. package/src/modules/catalog/commands/products.ts +84 -1
  405. package/src/modules/catalog/commands/variants.ts +7 -0
  406. package/src/modules/catalog/components/OmnibusSettings.tsx +832 -0
  407. package/src/modules/catalog/components/PriceEditorOmnibusRow.tsx +219 -0
  408. package/src/modules/catalog/components/lookupSources.ts +29 -0
  409. package/src/modules/catalog/components/prices/PriceScopeSelectors.tsx +9 -0
  410. package/src/modules/catalog/components/products/VariantBuilder.tsx +9 -0
  411. package/src/modules/catalog/data/entities.ts +123 -1
  412. package/src/modules/catalog/data/validators.ts +118 -0
  413. package/src/modules/catalog/di.ts +23 -0
  414. package/src/modules/catalog/i18n/de.json +77 -0
  415. package/src/modules/catalog/i18n/en.json +77 -0
  416. package/src/modules/catalog/i18n/es.json +77 -0
  417. package/src/modules/catalog/i18n/ko.json +77 -0
  418. package/src/modules/catalog/i18n/pl.json +77 -0
  419. package/src/modules/catalog/lib/omnibus.ts +406 -0
  420. package/src/modules/catalog/lib/omnibusBackfill.ts +198 -0
  421. package/src/modules/catalog/lib/omnibusCache.ts +111 -0
  422. package/src/modules/catalog/lib/omnibusConfig.ts +73 -0
  423. package/src/modules/catalog/lib/omnibusHistoryQueries.ts +248 -0
  424. package/src/modules/catalog/lib/omnibusPresentedEntry.ts +76 -0
  425. package/src/modules/catalog/lib/omnibusProductListEnrichment.ts +125 -0
  426. package/src/modules/catalog/lib/omnibusTypes.ts +202 -0
  427. package/src/modules/catalog/lib/optionSchemaTranslations.ts +55 -0
  428. package/src/modules/catalog/lib/priceHistoryQuery.ts +155 -0
  429. package/src/modules/catalog/lib/pricing.ts +42 -4
  430. package/src/modules/catalog/lib/productFilters.ts +349 -0
  431. package/src/modules/catalog/lib/productScopeKeys.ts +162 -0
  432. package/src/modules/catalog/lib/productScopeReindex.ts +139 -0
  433. package/src/modules/catalog/migrations/.snapshot-open-mercato.json +572 -0
  434. package/src/modules/catalog/migrations/Migration20261005105352_catalog.ts +39 -0
  435. package/src/modules/catalog/migrations/Migration20261005150824_catalog.ts +39 -0
  436. package/src/modules/catalog/services/catalogOmnibusService.ts +642 -0
  437. package/src/modules/catalog/setup.ts +2 -0
  438. package/src/modules/catalog/subscribers/product-scope-keys-category-deleted.ts +18 -0
  439. package/src/modules/catalog/subscribers/product-scope-keys-category-updated.ts +19 -0
  440. package/src/modules/catalog/translations.ts +12 -0
  441. package/src/modules/configs/i18n/de.json +5 -1
  442. package/src/modules/configs/i18n/en.json +5 -1
  443. package/src/modules/configs/i18n/es.json +5 -1
  444. package/src/modules/configs/i18n/ko.json +5 -1
  445. package/src/modules/configs/i18n/pl.json +5 -1
  446. package/src/modules/configs/lib/upgrade-actions.ts +35 -0
  447. package/src/modules/customer_groups/api/customer-groups/[id]/terms/route.ts +7 -0
  448. package/src/modules/customer_groups/components/CustomerGroupTermsSection.tsx +124 -2
  449. package/src/modules/customer_groups/components/customerGroupAssortmentScope.ts +46 -0
  450. package/src/modules/customer_groups/data/entities.ts +4 -0
  451. package/src/modules/customer_groups/data/validators.ts +14 -0
  452. package/src/modules/customer_groups/i18n/de.json +12 -0
  453. package/src/modules/customer_groups/i18n/en.json +12 -0
  454. package/src/modules/customer_groups/i18n/es.json +12 -0
  455. package/src/modules/customer_groups/i18n/ko.json +12 -0
  456. package/src/modules/customer_groups/i18n/pl.json +12 -0
  457. package/src/modules/customer_groups/migrations/.snapshot-open-mercato.json +17 -0
  458. package/src/modules/customer_groups/migrations/Migration20261005110250_customer_groups.ts +15 -0
  459. package/src/modules/customer_groups/services/customerGroupsService.ts +74 -23
  460. package/src/modules/ecommerce/acl.ts +9 -0
  461. package/src/modules/ecommerce/api/domain-mappings/route.ts +76 -0
  462. package/src/modules/ecommerce/api/openapi.ts +28 -0
  463. package/src/modules/ecommerce/api/store-channel-bindings/[id]/assortment-count/route.ts +141 -0
  464. package/src/modules/ecommerce/api/store-channel-bindings/crud.ts +283 -0
  465. package/src/modules/ecommerce/api/store-channel-bindings/route.ts +67 -0
  466. package/src/modules/ecommerce/api/store-domain-bindings/crud.ts +324 -0
  467. package/src/modules/ecommerce/api/store-domain-bindings/route.ts +73 -0
  468. package/src/modules/ecommerce/api/storefront/categories/[slug]/route.ts +94 -0
  469. package/src/modules/ecommerce/api/storefront/categories/openapiSchemas.ts +63 -0
  470. package/src/modules/ecommerce/api/storefront/categories/route.ts +78 -0
  471. package/src/modules/ecommerce/api/storefront/context/route.ts +203 -0
  472. package/src/modules/ecommerce/api/storefront/products/[idOrHandle]/route.ts +94 -0
  473. package/src/modules/ecommerce/api/storefront/products/openapiSchemas.ts +193 -0
  474. package/src/modules/ecommerce/api/storefront/products/route.ts +81 -0
  475. package/src/modules/ecommerce/api/storefront/search/suggest/openapiSchemas.ts +25 -0
  476. package/src/modules/ecommerce/api/storefront/search/suggest/route.ts +78 -0
  477. package/src/modules/ecommerce/api/storefront/storefrontRouteSupport.ts +68 -0
  478. package/src/modules/ecommerce/api/stores/[id]/branding/route.ts +205 -0
  479. package/src/modules/ecommerce/api/stores/[id]/preview-branding/route.ts +99 -0
  480. package/src/modules/ecommerce/api/stores/crud.ts +457 -0
  481. package/src/modules/ecommerce/api/stores/route.ts +66 -0
  482. package/src/modules/ecommerce/backend/config/ecommerce/[id]/page.meta.ts +12 -0
  483. package/src/modules/ecommerce/backend/config/ecommerce/[id]/page.tsx +5 -0
  484. package/src/modules/ecommerce/backend/config/ecommerce/page.meta.ts +12 -0
  485. package/src/modules/ecommerce/backend/config/ecommerce/page.tsx +5 -0
  486. package/src/modules/ecommerce/commands/stores.ts +154 -0
  487. package/src/modules/ecommerce/components/ChannelAssortmentCount.tsx +138 -0
  488. package/src/modules/ecommerce/components/ChannelBindingFormSections.tsx +40 -0
  489. package/src/modules/ecommerce/components/ChannelLookupPicker.tsx +5 -0
  490. package/src/modules/ecommerce/components/DomainStatusWarning.tsx +32 -0
  491. package/src/modules/ecommerce/components/StoreAvailabilityDefaultsSection.tsx +219 -0
  492. package/src/modules/ecommerce/components/StoreBrandingControls.tsx +73 -0
  493. package/src/modules/ecommerce/components/StoreBrandingPreview.tsx +77 -0
  494. package/src/modules/ecommerce/components/StoreBrandingTab.tsx +242 -0
  495. package/src/modules/ecommerce/components/StoreChannelBindingDialog.tsx +306 -0
  496. package/src/modules/ecommerce/components/StoreChannelsTab.tsx +375 -0
  497. package/src/modules/ecommerce/components/StoreCreateDialog.tsx +204 -0
  498. package/src/modules/ecommerce/components/StoreDomainBindingDialog.tsx +198 -0
  499. package/src/modules/ecommerce/components/StoreDomainsTab.tsx +426 -0
  500. package/src/modules/ecommerce/components/StoreEditPage.tsx +193 -0
  501. package/src/modules/ecommerce/components/StoreGeneralTab.tsx +189 -0
  502. package/src/modules/ecommerce/components/StoreListPage.tsx +326 -0
  503. package/src/modules/ecommerce/components/StoreSeoTab.tsx +152 -0
  504. package/src/modules/ecommerce/components/StoreStatusBadge.tsx +22 -0
  505. package/src/modules/ecommerce/components/channelLookups.ts +24 -0
  506. package/src/modules/ecommerce/components/storeAdmin.ts +56 -0
  507. package/src/modules/ecommerce/components/storeBrandingForm.ts +230 -0
  508. package/src/modules/ecommerce/components/storeChannels.ts +210 -0
  509. package/src/modules/ecommerce/components/storeDomains.ts +221 -0
  510. package/src/modules/ecommerce/components/storeEditTabs.tsx +76 -0
  511. package/src/modules/ecommerce/components/storeGeneral.ts +155 -0
  512. package/src/modules/ecommerce/components/storeSeo.ts +78 -0
  513. package/src/modules/ecommerce/components/useChannelAssortmentCount.ts +67 -0
  514. package/src/modules/ecommerce/components/useStoreAccess.ts +36 -0
  515. package/src/modules/ecommerce/data/enrichers.ts +213 -0
  516. package/src/modules/ecommerce/data/entities.ts +168 -0
  517. package/src/modules/ecommerce/data/validators.ts +587 -0
  518. package/src/modules/ecommerce/di.ts +9 -0
  519. package/src/modules/ecommerce/events.ts +27 -0
  520. package/src/modules/ecommerce/i18n/de.json +341 -0
  521. package/src/modules/ecommerce/i18n/en.json +341 -0
  522. package/src/modules/ecommerce/i18n/es.json +341 -0
  523. package/src/modules/ecommerce/i18n/ko.json +341 -0
  524. package/src/modules/ecommerce/i18n/pl.json +341 -0
  525. package/src/modules/ecommerce/index.ts +14 -0
  526. package/src/modules/ecommerce/lib/assortmentCount.ts +175 -0
  527. package/src/modules/ecommerce/lib/brandingStyles.ts +215 -0
  528. package/src/modules/ecommerce/lib/buyerContext.ts +401 -0
  529. package/src/modules/ecommerce/lib/cacheInvalidation.ts +302 -0
  530. package/src/modules/ecommerce/lib/cacheKeys.ts +340 -0
  531. package/src/modules/ecommerce/lib/createConflictRecheck.ts +31 -0
  532. package/src/modules/ecommerce/lib/crudEvents.ts +128 -0
  533. package/src/modules/ecommerce/lib/crudSupport.ts +85 -0
  534. package/src/modules/ecommerce/lib/domainBindingRebind.ts +138 -0
  535. package/src/modules/ecommerce/lib/domainMappingSummaries.ts +71 -0
  536. package/src/modules/ecommerce/lib/exclusiveFlag.ts +90 -0
  537. package/src/modules/ecommerce/lib/references.ts +93 -0
  538. package/src/modules/ecommerce/lib/seedDraftStore.ts +125 -0
  539. package/src/modules/ecommerce/lib/storeBindingCascade.ts +138 -0
  540. package/src/modules/ecommerce/lib/storeBranding.ts +48 -0
  541. package/src/modules/ecommerce/lib/storeBrandingRoute.ts +91 -0
  542. package/src/modules/ecommerce/lib/storeContext.ts +669 -0
  543. package/src/modules/ecommerce/lib/storeContextService.ts +71 -0
  544. package/src/modules/ecommerce/lib/storeLocale.ts +88 -0
  545. package/src/modules/ecommerce/lib/storeNotifications.ts +55 -0
  546. package/src/modules/ecommerce/lib/storefrontCatalogSupport.ts +427 -0
  547. package/src/modules/ecommerce/lib/storefrontCategories.ts +419 -0
  548. package/src/modules/ecommerce/lib/storefrontCategoryCache.ts +113 -0
  549. package/src/modules/ecommerce/lib/storefrontDetail.ts +654 -0
  550. package/src/modules/ecommerce/lib/storefrontFacets.ts +697 -0
  551. package/src/modules/ecommerce/lib/storefrontOptionFilter.ts +44 -0
  552. package/src/modules/ecommerce/lib/storefrontPricing.ts +583 -0
  553. package/src/modules/ecommerce/lib/storefrontProductCache.ts +126 -0
  554. package/src/modules/ecommerce/lib/storefrontProductScope.ts +181 -0
  555. package/src/modules/ecommerce/lib/storefrontProducts.ts +963 -0
  556. package/src/modules/ecommerce/lib/storefrontQuery.ts +215 -0
  557. package/src/modules/ecommerce/lib/storefrontRateLimit.ts +126 -0
  558. package/src/modules/ecommerce/lib/storefrontSearch.ts +454 -0
  559. package/src/modules/ecommerce/lib/storefrontSearchCache.ts +49 -0
  560. package/src/modules/ecommerce/lib/subscriberSupport.ts +41 -0
  561. package/src/modules/ecommerce/lib/types.ts +69 -0
  562. package/src/modules/ecommerce/migrations/.snapshot-open-mercato.json +829 -0
  563. package/src/modules/ecommerce/migrations/Migration20261005113233_ecommerce.ts +32 -0
  564. package/src/modules/ecommerce/migrations/Migration20261006112733_ecommerce.ts +15 -0
  565. package/src/modules/ecommerce/notifications.client.ts +46 -0
  566. package/src/modules/ecommerce/notifications.ts +46 -0
  567. package/src/modules/ecommerce/search.ts +93 -0
  568. package/src/modules/ecommerce/setup.ts +15 -0
  569. package/src/modules/ecommerce/subscribers/assortment-empty-notifications.ts +12 -0
  570. package/src/modules/ecommerce/subscribers/availability-policy-cache-invalidation.ts +12 -0
  571. package/src/modules/ecommerce/subscribers/catalog-category-cache-invalidation.ts +12 -0
  572. package/src/modules/ecommerce/subscribers/catalog-price-cache-invalidation.ts +12 -0
  573. package/src/modules/ecommerce/subscribers/catalog-product-cache-invalidation.ts +12 -0
  574. package/src/modules/ecommerce/subscribers/catalog-variant-cache-invalidation.ts +12 -0
  575. package/src/modules/ecommerce/subscribers/customer-group-cache-invalidation.ts +12 -0
  576. package/src/modules/ecommerce/subscribers/customer-group-membership-cache-invalidation.ts +12 -0
  577. package/src/modules/ecommerce/subscribers/customer-group-terms-cache-invalidation.ts +12 -0
  578. package/src/modules/ecommerce/subscribers/customer-user-deleted-cache-invalidation.ts +12 -0
  579. package/src/modules/ecommerce/subscribers/customer-user-updated-cache-invalidation.ts +12 -0
  580. package/src/modules/ecommerce/subscribers/domain-binding-rebind.ts +14 -0
  581. package/src/modules/ecommerce/subscribers/domain-mapping-cache-invalidation.ts +12 -0
  582. package/src/modules/ecommerce/subscribers/store-cache-invalidation.ts +12 -0
  583. package/src/modules/ecommerce/subscribers/store-channel-binding-cache-invalidation.ts +12 -0
  584. package/src/modules/ecommerce/subscribers/store-domain-binding-cache-invalidation.ts +12 -0
  585. package/src/modules/ecommerce/subscribers/store-notifications.ts +12 -0
  586. package/src/modules/query_index/lib/batch.ts +25 -9
  587. package/src/modules/query_index/lib/doc-enrichers.ts +306 -0
  588. package/src/modules/query_index/lib/document.ts +2 -0
  589. package/src/modules/query_index/lib/engine.ts +58 -1
  590. package/src/modules/query_index/lib/indexer.ts +8 -0
  591. package/src/modules/query_index/lib/search-tokens.ts +2 -0
  592. package/src/modules/query_index/migrations/Migration20261005201500_query_index.ts +42 -0
  593. package/src/modules/translations/components/TranslationManager.tsx +3 -3
  594. package/src/modules/translations/data/validators.ts +1 -1
  595. package/src/modules/translations/lib/batch.ts +47 -0
  596. package/src/modules/translations/lib/helpers.ts +4 -0
  597. package/src/modules/translations/lib/resolve-field-list.ts +16 -2
@@ -0,0 +1,302 @@
1
+ import type { EntityManager } from '@mikro-orm/postgresql'
2
+ import { createLogger } from '@open-mercato/shared/lib/logger'
3
+ import { getTelemetryRuntime } from '@open-mercato/shared/lib/telemetry/runtime'
4
+ import {
5
+ catalogCategoryTag,
6
+ catalogPriceTag,
7
+ catalogProductTag,
8
+ catalogProductsTag,
9
+ ecommerceCustomerGroupTag,
10
+ ecommerceCustomerTag,
11
+ ecommerceCustomerUserTag,
12
+ ecommerceDomainMappingTag,
13
+ ecommerceDomainTag,
14
+ ecommerceStoreTag,
15
+ ecommerceUngroupedBuyerTag,
16
+ invalidateEcommerceCacheTags,
17
+ storefrontAvailabilityTag,
18
+ } from './cacheKeys'
19
+ import {
20
+ asPayloadRecord,
21
+ eventAction,
22
+ readEventScope,
23
+ readPayloadString,
24
+ tryResolveService,
25
+ type EcommerceSubscriberContext,
26
+ } from './subscriberSupport'
27
+
28
+ /**
29
+ * Event → cache-tag mapping for the storefront caches (SPEC-029 §8 table). Every eviction goes
30
+ * through `invalidateEcommerceCacheTags`, which clears the tenant-agnostic resolution cache and the
31
+ * emitting tenant's buyer/storefront caches.
32
+ */
33
+
34
+ const logger = createLogger('ecommerce').child({ component: 'cache-invalidation' })
35
+
36
+ type TagDeriver = (payload: Record<string, unknown>, action: string | null, tenantId: string | null) => string[]
37
+
38
+ const STORE_CACHE_ACTIONS: ReadonlySet<string> = new Set(['created', 'updated', 'deleted', 'branding_updated'])
39
+ const CUSTOMER_GROUP_CACHE_ACTIONS: ReadonlySet<string> = new Set(['created', 'updated', 'deleted'])
40
+ const CUSTOMER_GROUP_DEFAULT_CANDIDATE_ACTIONS: ReadonlySet<string> = new Set(['created', 'updated'])
41
+ const CATALOG_CRUD_ACTIONS: ReadonlySet<string> = new Set(['created', 'updated', 'deleted'])
42
+ const CUSTOMER_USER_CACHE_ACTIONS: ReadonlySet<string> = new Set(['updated', 'deleted'])
43
+
44
+ function tagsFrom(values: Array<string | null>, toTag: (value: string) => string): string[] {
45
+ return values.filter((value): value is string => value !== null).map(toTag)
46
+ }
47
+
48
+ export const storeEventTags: TagDeriver = (payload, action) => {
49
+ if (action && !STORE_CACHE_ACTIONS.has(action)) return []
50
+ return tagsFrom([readPayloadString(payload, 'id')], ecommerceStoreTag)
51
+ }
52
+
53
+ export const storeDomainBindingEventTags: TagDeriver = (payload) => [
54
+ ...tagsFrom(
55
+ [readPayloadString(payload, 'storeId'), readPayloadString(payload, 'previousStoreId')],
56
+ ecommerceStoreTag,
57
+ ),
58
+ ...tagsFrom(
59
+ [readPayloadString(payload, 'domainMappingId'), readPayloadString(payload, 'previousDomainMappingId')],
60
+ ecommerceDomainMappingTag,
61
+ ),
62
+ ]
63
+
64
+ export const storeChannelBindingEventTags: TagDeriver = (payload) =>
65
+ tagsFrom([readPayloadString(payload, 'storeId'), readPayloadString(payload, 'previousStoreId')], ecommerceStoreTag)
66
+
67
+ export const domainMappingEventTags: TagDeriver = (payload) => [
68
+ ...tagsFrom(
69
+ [readPayloadString(payload, 'id'), readPayloadString(payload, 'replacedDomainId')],
70
+ ecommerceDomainMappingTag,
71
+ ),
72
+ ...tagsFrom(
73
+ [readPayloadString(payload, 'hostname'), readPayloadString(payload, 'replacedHostname')],
74
+ ecommerceDomainTag,
75
+ ),
76
+ ]
77
+
78
+ /**
79
+ * `customer_accounts.user.updated|deleted`: the user's buyer contexts, so a deactivated or deleted
80
+ * portal user stops resolving to their cached identity (and contract prices) before the TTL ends.
81
+ */
82
+ export const customerUserEventTags: TagDeriver = (payload, action) => {
83
+ if (action && !CUSTOMER_USER_CACHE_ACTIONS.has(action)) return []
84
+ return tagsFrom([readPayloadString(payload, 'id')], ecommerceCustomerUserTag)
85
+ }
86
+
87
+ export const customerGroupMembershipEventTags: TagDeriver = (payload) =>
88
+ tagsFrom([readPayloadString(payload, 'customerId')], ecommerceCustomerTag)
89
+
90
+ /**
91
+ * `customer_groups.group.*`: the group's buyer contexts. The payload carries no `isDefault`, so a
92
+ * create or update also evicts the tenant's ungrouped buyer contexts, which would fall back to the
93
+ * group if it became the tenant default; a replaced default is announced as its own update.
94
+ */
95
+ export const customerGroupEventTags: TagDeriver = (payload, action, tenantId) => {
96
+ if (action && !CUSTOMER_GROUP_CACHE_ACTIONS.has(action)) return []
97
+ const tags = action === 'created' ? [] : tagsFrom([readPayloadString(payload, 'id')], ecommerceCustomerGroupTag)
98
+ if (tenantId && (!action || CUSTOMER_GROUP_DEFAULT_CANDIDATE_ACTIONS.has(action))) {
99
+ tags.push(ecommerceUngroupedBuyerTag(tenantId))
100
+ }
101
+ return tags
102
+ }
103
+
104
+ export const customerGroupTermsEventTags: TagDeriver = (payload) =>
105
+ tagsFrom([readPayloadString(payload, 'groupId')], ecommerceCustomerGroupTag)
106
+
107
+ export async function invalidateForEvent(
108
+ payload: unknown,
109
+ ctx: EcommerceSubscriberContext,
110
+ deriveTags: TagDeriver,
111
+ ): Promise<number> {
112
+ const record = asPayloadRecord(payload)
113
+ const { tenantId } = readEventScope(record, ctx)
114
+ const tags = deriveTags(record, eventAction(ctx.eventName), tenantId)
115
+ if (tags.length === 0) return 0
116
+ return invalidateEcommerceCacheTags(ctx, { tenantId, tags })
117
+ }
118
+
119
+ type CatalogLookupDatabase = {
120
+ catalog_product_variant_prices: {
121
+ id: string
122
+ tenant_id: string
123
+ organization_id: string
124
+ customer_id: string | null
125
+ product_id: string | null
126
+ variant_id: string | null
127
+ }
128
+ catalog_product_variants: {
129
+ id: string
130
+ tenant_id: string
131
+ organization_id: string
132
+ product_id: string
133
+ }
134
+ availability_policies: {
135
+ id: string
136
+ tenant_id: string
137
+ organization_id: string
138
+ product_id: string | null
139
+ }
140
+ }
141
+
142
+ type PriceOwner = { customerId: string | null; productId: string | null }
143
+
144
+ type LookupScope = { tenantId: string; organizationId: string | null }
145
+
146
+ async function readPriceOwner(em: EntityManager, priceId: string, scope: LookupScope): Promise<PriceOwner | null> {
147
+ let query = em
148
+ .getKysely<CatalogLookupDatabase>()
149
+ .selectFrom('catalog_product_variant_prices as price')
150
+ .leftJoin('catalog_product_variants as variant', 'variant.id', 'price.variant_id')
151
+ .select(['price.customer_id as customerId', 'price.product_id as productId', 'variant.product_id as variantProductId'])
152
+ .where('price.id', '=', priceId)
153
+ .where('price.tenant_id', '=', scope.tenantId)
154
+ if (scope.organizationId) query = query.where('price.organization_id', '=', scope.organizationId)
155
+ const row = await query.executeTakeFirst()
156
+ if (!row) return null
157
+ return { customerId: row.customerId ?? null, productId: row.productId ?? row.variantProductId ?? null }
158
+ }
159
+
160
+ async function readVariantProductId(em: EntityManager, variantId: string, scope: LookupScope): Promise<string | null> {
161
+ let query = em
162
+ .getKysely<CatalogLookupDatabase>()
163
+ .selectFrom('catalog_product_variants')
164
+ .select('product_id')
165
+ .where('id', '=', variantId)
166
+ .where('tenant_id', '=', scope.tenantId)
167
+ if (scope.organizationId) query = query.where('organization_id', '=', scope.organizationId)
168
+ const row = await query.executeTakeFirst()
169
+ return row?.product_id ?? null
170
+ }
171
+
172
+ async function readPolicyProductId(em: EntityManager, policyId: string, scope: LookupScope): Promise<string | null> {
173
+ let query = em
174
+ .getKysely<CatalogLookupDatabase>()
175
+ .selectFrom('availability_policies')
176
+ .select('product_id')
177
+ .where('id', '=', policyId)
178
+ .where('tenant_id', '=', scope.tenantId)
179
+ if (scope.organizationId) query = query.where('organization_id', '=', scope.organizationId)
180
+ const row = await query.executeTakeFirst()
181
+ return row?.product_id ?? null
182
+ }
183
+
184
+ /**
185
+ * Reads the row an id-only catalog/availability payload points at. A missing `em`, a missing row
186
+ * and a failed lookup all answer `undefined`, so the caller falls back to its broader tag.
187
+ */
188
+ async function lookupForEvent<T>(
189
+ ctx: EcommerceSubscriberContext,
190
+ id: string,
191
+ scope: LookupScope,
192
+ lookup: (em: EntityManager, id: string, scope: LookupScope) => Promise<T | null>,
193
+ failureCode: string,
194
+ ): Promise<T | undefined> {
195
+ const em = tryResolveService<EntityManager>(ctx, 'em')
196
+ if (!em) return undefined
197
+ try {
198
+ return (await lookup(em, id, scope)) ?? undefined
199
+ } catch (error) {
200
+ logger.warn('Storefront cache lookup failed; falling back to broader tags', { id, failureCode, err: error })
201
+ getTelemetryRuntime()?.reportError(error, {
202
+ module: 'ecommerce',
203
+ code: failureCode,
204
+ attributes: { id },
205
+ })
206
+ return undefined
207
+ }
208
+ }
209
+
210
+ type ScopedEvent = { id: string; action: string | null; scope: LookupScope }
211
+
212
+ function readScopedEvent(payload: unknown, ctx: EcommerceSubscriberContext): ScopedEvent | null {
213
+ const record = asPayloadRecord(payload)
214
+ const id = readPayloadString(record, 'id')
215
+ const scope = readEventScope(record, ctx)
216
+ if (!id || !scope.tenantId) return null
217
+ return { id, action: eventAction(ctx.eventName), scope: { tenantId: scope.tenantId, organizationId: scope.organizationId } }
218
+ }
219
+
220
+ function isCatalogCrudAction(action: string | null): boolean {
221
+ return action === null || CATALOG_CRUD_ACTIONS.has(action)
222
+ }
223
+
224
+ /**
225
+ * `catalog.price.*` (§8 buyer context, Storefront Public API §9): evicts the product's
226
+ * `catalog-price` tag and every listing of the tenant, plus the owning customer's buyer context
227
+ * when the row is customer-specific (`customerOverlayId`). The payload carries ids only, so the row
228
+ * is read back; a deleted row is gone, so a delete evicts the listings only and the product detail
229
+ * and buyer context fall back to their TTLs (catalog declares no price-kind events either).
230
+ */
231
+ export async function invalidateForPriceEvent(payload: unknown, ctx: EcommerceSubscriberContext): Promise<number> {
232
+ const event = readScopedEvent(payload, ctx)
233
+ if (!event || !isCatalogCrudAction(event.action)) return 0
234
+ const owner =
235
+ event.action === 'deleted'
236
+ ? undefined
237
+ : await lookupForEvent(ctx, event.id, event.scope, readPriceOwner, 'ecommerce.price_cache_invalidation_failed')
238
+ const tags = [catalogProductsTag(event.scope.tenantId)]
239
+ if (owner?.productId) tags.push(catalogPriceTag(owner.productId))
240
+ if (owner?.customerId) tags.push(ecommerceCustomerTag(owner.customerId))
241
+ return invalidateEcommerceCacheTags(ctx, { tenantId: event.scope.tenantId, tags })
242
+ }
243
+
244
+ /** `catalog.product.created|updated|deleted`: the product's detail entries and every listing of the tenant. */
245
+ export async function invalidateForProductEvent(payload: unknown, ctx: EcommerceSubscriberContext): Promise<number> {
246
+ const event = readScopedEvent(payload, ctx)
247
+ if (!event || !isCatalogCrudAction(event.action)) return 0
248
+ return invalidateEcommerceCacheTags(ctx, {
249
+ tenantId: event.scope.tenantId,
250
+ tags: [catalogProductTag(event.id), catalogProductsTag(event.scope.tenantId)],
251
+ })
252
+ }
253
+
254
+ /**
255
+ * `catalog.variant.*`: the variant's product (read back — the payload carries ids only) and every
256
+ * listing of the tenant. A deleted variant row is gone, so its product detail falls back to its TTL.
257
+ */
258
+ export async function invalidateForVariantEvent(payload: unknown, ctx: EcommerceSubscriberContext): Promise<number> {
259
+ const event = readScopedEvent(payload, ctx)
260
+ if (!event || !isCatalogCrudAction(event.action)) return 0
261
+ const productId =
262
+ event.action === 'deleted'
263
+ ? undefined
264
+ : await lookupForEvent(ctx, event.id, event.scope, readVariantProductId, 'ecommerce.variant_cache_invalidation_failed')
265
+ const tags = [catalogProductsTag(event.scope.tenantId)]
266
+ if (productId) tags.push(catalogProductTag(productId))
267
+ return invalidateEcommerceCacheTags(ctx, { tenantId: event.scope.tenantId, tags })
268
+ }
269
+
270
+ /** `catalog.category.*`: details showing the category and every listing (category names, filters, assortment). */
271
+ export async function invalidateForCategoryEvent(payload: unknown, ctx: EcommerceSubscriberContext): Promise<number> {
272
+ const event = readScopedEvent(payload, ctx)
273
+ if (!event || !isCatalogCrudAction(event.action)) return 0
274
+ return invalidateEcommerceCacheTags(ctx, {
275
+ tenantId: event.scope.tenantId,
276
+ tags: [catalogCategoryTag(event.id), catalogProductsTag(event.scope.tenantId)],
277
+ })
278
+ }
279
+
280
+ /**
281
+ * `availability.policy.*`: a policy tied to a product evicts that product and every listing; a
282
+ * store- or tenant-wide policy (or one that cannot be read back) evicts every storefront product
283
+ * entry of the tenant. Policies are soft-deleted, so a delete still reads back its product.
284
+ */
285
+ export async function invalidateForAvailabilityPolicyEvent(
286
+ payload: unknown,
287
+ ctx: EcommerceSubscriberContext,
288
+ ): Promise<number> {
289
+ const event = readScopedEvent(payload, ctx)
290
+ if (!event || !isCatalogCrudAction(event.action)) return 0
291
+ const productId = await lookupForEvent(
292
+ ctx,
293
+ event.id,
294
+ event.scope,
295
+ readPolicyProductId,
296
+ 'ecommerce.availability_policy_cache_invalidation_failed',
297
+ )
298
+ const tags = productId
299
+ ? [catalogProductTag(productId), catalogProductsTag(event.scope.tenantId)]
300
+ : [storefrontAvailabilityTag(event.scope.tenantId)]
301
+ return invalidateEcommerceCacheTags(ctx, { tenantId: event.scope.tenantId, tags })
302
+ }
@@ -0,0 +1,340 @@
1
+ import { createHash } from 'node:crypto'
2
+ import { runWithCacheTenant, type CacheStrategy } from '@open-mercato/cache'
3
+ import { createLogger } from '@open-mercato/shared/lib/logger'
4
+ import { getTelemetryRuntime } from '@open-mercato/shared/lib/telemetry/runtime'
5
+ import type { BuyerContext, StoreContext } from './types'
6
+
7
+ /**
8
+ * The ONLY file in the ecommerce module allowed to resolve the DI `cache` (SPEC-029 §6.1
9
+ * "Enforcement", D7). Every storefront key is built by `buildStorefrontCacheKey`; callers pass
10
+ * `parts`, never a raw key. `__tests__/no-raw-cache-access.test.ts` fails on any other access.
11
+ */
12
+
13
+ export type StorefrontCacheScope = 'digest' | 'assortment' | 'priceScope' | 'store'
14
+
15
+ export type StorefrontCacheKeyOptions = { scope?: StorefrontCacheScope }
16
+
17
+ export type StorefrontCacheSetOptions = {
18
+ ttlMs: number
19
+ tags?: string[]
20
+ scope?: StorefrontCacheScope
21
+ }
22
+
23
+ export type StorefrontCache = {
24
+ get<T>(parts: string[], options?: StorefrontCacheKeyOptions): Promise<T | null>
25
+ set<T>(parts: string[], value: T, options: StorefrontCacheSetOptions): Promise<void>
26
+ deleteByTags(tags: string[]): Promise<number>
27
+ }
28
+
29
+ export type EcommerceResolutionCacheSetOptions = { ttlMs: number; tags?: string[] }
30
+
31
+ /**
32
+ * Store-resolution cache (host/slug → store + default channel binding, §8.1 steps 3–5). It runs
33
+ * BEFORE a `StoreContext` exists, so it is tenant-agnostic and MUST NEVER carry buyer data.
34
+ */
35
+ export type EcommerceResolutionCache = {
36
+ get<T>(parts: string[]): Promise<T | null>
37
+ set<T>(parts: string[], value: T, options: EcommerceResolutionCacheSetOptions): Promise<void>
38
+ deleteByTags(tags: string[]): Promise<number>
39
+ }
40
+
41
+ /**
42
+ * Buyer-context cache (§8, step 6): one entry per (store, customerUserId | anonymous), holding the
43
+ * locale-independent buyer layer only. Tagged per store, per portal user, per identity in
44
+ * `customerIds` and per contributing group so user, membership, terms, group and price events can
45
+ * evict it.
46
+ */
47
+ export type BuyerContextCache = {
48
+ get(customerUserId: string | null): Promise<BuyerContext | null>
49
+ set(buyer: BuyerContext): Promise<void>
50
+ deleteByTags(tags: string[]): Promise<number>
51
+ }
52
+
53
+ export type BuyerContextCacheScope = { tenantId: string; storeId: string }
54
+
55
+ export type CacheContainer = { resolve: (name: string) => unknown }
56
+
57
+ export const BUYER_CONTEXT_TTL_MS = 60_000
58
+
59
+ const STOREFRONT_KEY_NAMESPACE = 'ecommerce:storefront'
60
+ const RESOLUTION_KEY_NAMESPACE = 'ecommerce:resolution'
61
+ const BUYER_KEY_NAMESPACE = 'ecommerce:buyer'
62
+ const ANONYMOUS_SEGMENT = 'anonymous'
63
+ const NULL_SEGMENT = '-'
64
+
65
+ const SCOPE_SEGMENTS: Record<StorefrontCacheScope, string> = {
66
+ digest: 'digest',
67
+ assortment: 'assortment',
68
+ priceScope: 'price-scope',
69
+ store: 'store',
70
+ }
71
+
72
+ const logger = createLogger('ecommerce').child({ component: 'storefront-cache' })
73
+
74
+ function encodeSegment(value: string): string {
75
+ return encodeURIComponent(value)
76
+ }
77
+
78
+ function encodeParts(parts: string[]): string {
79
+ if (parts.length === 0) {
80
+ throw new Error('[internal] ecommerce cache keys require at least one part')
81
+ }
82
+ return parts.map(encodeSegment).join(':')
83
+ }
84
+
85
+ function scopeSegments(ctx: StoreContext, scope: StorefrontCacheScope): string[] {
86
+ const storeId = ctx.store.id
87
+ switch (scope) {
88
+ case 'digest':
89
+ return [storeId, ctx.digest]
90
+ case 'assortment':
91
+ return [storeId, ctx.effectiveLocale, ctx.buyer.assortmentScopeHash]
92
+ case 'priceScope':
93
+ return [storeId, ctx.buyer.priceScopeKey, ctx.buyer.customerOverlayId ?? NULL_SEGMENT]
94
+ case 'store':
95
+ return [storeId, ctx.effectiveLocale]
96
+ }
97
+ }
98
+
99
+ export function buildStorefrontCacheKey(
100
+ ctx: StoreContext,
101
+ parts: string[],
102
+ options?: StorefrontCacheKeyOptions,
103
+ ): string {
104
+ const scope = options?.scope ?? 'digest'
105
+ const prefix = [SCOPE_SEGMENTS[scope], ...scopeSegments(ctx, scope).map(encodeSegment)].join(':')
106
+ return `${STOREFRONT_KEY_NAMESPACE}:${prefix}:${encodeParts(parts)}`
107
+ }
108
+
109
+ function canonicalCacheValue(value: unknown): unknown {
110
+ if (Array.isArray(value)) return value.map(canonicalCacheValue)
111
+ if (value && typeof value === 'object') {
112
+ return Object.fromEntries(
113
+ Object.entries(value as Record<string, unknown>)
114
+ .filter((entry) => entry[1] !== undefined)
115
+ .sort(([left], [right]) => (left < right ? -1 : left > right ? 1 : 0))
116
+ .map(([key, entry]) => [key, canonicalCacheValue(entry)]),
117
+ )
118
+ }
119
+ return value
120
+ }
121
+
122
+ /** A fixed-length key part for a structured value: object keys sorted, `undefined` entries dropped, sha256-hashed. */
123
+ export function storefrontCacheValueHash(value: unknown): string {
124
+ return createHash('sha256').update(JSON.stringify(canonicalCacheValue(value))).digest('hex')
125
+ }
126
+
127
+ export function buildEcommerceResolutionCacheKey(parts: string[]): string {
128
+ return `${RESOLUTION_KEY_NAMESPACE}:${encodeParts(parts)}`
129
+ }
130
+
131
+ export function buildBuyerContextCacheKey(storeId: string, customerUserId: string | null): string {
132
+ return `${BUYER_KEY_NAMESPACE}:${encodeParts([storeId, customerUserId ?? ANONYMOUS_SEGMENT])}`
133
+ }
134
+
135
+ export function ecommerceStoreTag(storeId: string): string {
136
+ return `ecommerce-store:${storeId}`
137
+ }
138
+
139
+ export function ecommerceDomainMappingTag(domainMappingId: string): string {
140
+ return `ecommerce-domain-mapping:${domainMappingId}`
141
+ }
142
+
143
+ export function ecommerceDomainTag(hostname: string): string {
144
+ return `ecommerce-domain:${hostname}`
145
+ }
146
+
147
+ export function ecommerceCustomerTag(customerId: string): string {
148
+ return `customer:${customerId}`
149
+ }
150
+
151
+ /** One portal user's buyer contexts: evicted when the user is updated (e.g. deactivated) or deleted. */
152
+ export function ecommerceCustomerUserTag(customerUserId: string): string {
153
+ return `customer-user:${customerUserId}`
154
+ }
155
+
156
+ export function ecommerceCustomerGroupTag(customerGroupId: string): string {
157
+ return `customer-group:${customerGroupId}`
158
+ }
159
+
160
+ export function catalogProductTag(productId: string): string {
161
+ return `catalog-product:${productId}`
162
+ }
163
+
164
+ /** Every storefront listing of a tenant: evicted by any product, variant, category or price change. */
165
+ export function catalogProductsTag(tenantId: string): string {
166
+ return `catalog-products:${tenantId}`
167
+ }
168
+
169
+ export function catalogCategoryTag(categoryId: string): string {
170
+ return `catalog-category:${categoryId}`
171
+ }
172
+
173
+ export function catalogPriceTag(productId: string): string {
174
+ return `catalog-price:${productId}`
175
+ }
176
+
177
+ /** Every storefront product entry of a tenant: evicted by an availability policy not tied to one product. */
178
+ export function storefrontAvailabilityTag(tenantId: string): string {
179
+ return `availability:${tenantId}`
180
+ }
181
+
182
+ /**
183
+ * Buyer contexts of a tenant that resolved to no customer group (anonymous or unassigned buyers
184
+ * while the tenant has no default group): evicted when a group is created or updated, since that
185
+ * group may have become the default they fall back to.
186
+ */
187
+ export function ecommerceUngroupedBuyerTag(tenantId: string): string {
188
+ return `customer-group-none:${tenantId}`
189
+ }
190
+
191
+ export function buyerContextTags(
192
+ buyer: Pick<BuyerContext, 'customerIds' | 'customerGroupIds'> & { customerUserId?: string | null },
193
+ tenantId?: string,
194
+ ): string[] {
195
+ return [
196
+ ...(buyer.customerUserId ? [ecommerceCustomerUserTag(buyer.customerUserId)] : []),
197
+ ...buyer.customerIds.map(ecommerceCustomerTag),
198
+ ...buyer.customerGroupIds.map(ecommerceCustomerGroupTag),
199
+ ...(tenantId && buyer.customerGroupIds.length === 0 ? [ecommerceUngroupedBuyerTag(tenantId)] : []),
200
+ ]
201
+ }
202
+
203
+ function resolveCacheService(container: CacheContainer | null | undefined): CacheStrategy | null {
204
+ if (!container?.resolve) return null
205
+ try {
206
+ const candidate = container.resolve('cache') as CacheStrategy | null | undefined
207
+ if (
208
+ candidate &&
209
+ typeof candidate.get === 'function' &&
210
+ typeof candidate.set === 'function' &&
211
+ typeof candidate.deleteByTags === 'function'
212
+ ) {
213
+ return candidate
214
+ }
215
+ } catch {
216
+ return null
217
+ }
218
+ return null
219
+ }
220
+
221
+ function reportCacheFailure(operation: string, error: unknown): void {
222
+ logger.warn('Ecommerce cache operation failed; degrading to a cache miss', { operation, err: error })
223
+ getTelemetryRuntime()?.reportError(error, {
224
+ module: 'ecommerce',
225
+ code: 'ecommerce.cache_operation_failed',
226
+ attributes: { operation },
227
+ })
228
+ }
229
+
230
+ type TenantScopedCacheOptions = {
231
+ cache: CacheStrategy | null
232
+ tenantId: string | null
233
+ baseTags: string[]
234
+ }
235
+
236
+ function createTenantScopedAccess({ cache, tenantId, baseTags }: TenantScopedCacheOptions) {
237
+ return {
238
+ async get<T>(key: string): Promise<T | null> {
239
+ if (!cache) return null
240
+ try {
241
+ const value = await runWithCacheTenant(tenantId, () => cache.get(key))
242
+ return (value ?? null) as T | null
243
+ } catch (error) {
244
+ reportCacheFailure('get', error)
245
+ return null
246
+ }
247
+ },
248
+ async set<T>(key: string, value: T, ttlMs: number, tags: string[] | undefined): Promise<void> {
249
+ if (!cache) return
250
+ const mergedTags = Array.from(new Set([...baseTags, ...(tags ?? [])]))
251
+ try {
252
+ await runWithCacheTenant(tenantId, () => cache.set(key, value, { ttl: ttlMs, tags: mergedTags }))
253
+ } catch (error) {
254
+ reportCacheFailure('set', error)
255
+ }
256
+ },
257
+ async deleteByTags(tags: string[]): Promise<number> {
258
+ if (!cache || tags.length === 0) return 0
259
+ try {
260
+ return await runWithCacheTenant(tenantId, () => cache.deleteByTags(tags))
261
+ } catch (error) {
262
+ reportCacheFailure('deleteByTags', error)
263
+ return 0
264
+ }
265
+ },
266
+ }
267
+ }
268
+
269
+ export function storefrontCache(container: CacheContainer | null | undefined, ctx: StoreContext): StorefrontCache {
270
+ const access = createTenantScopedAccess({
271
+ cache: resolveCacheService(container),
272
+ tenantId: ctx.tenantId,
273
+ baseTags: [ecommerceStoreTag(ctx.store.id)],
274
+ })
275
+ return {
276
+ get: (parts, options) => access.get(buildStorefrontCacheKey(ctx, parts, options)),
277
+ set: (parts, value, options) =>
278
+ access.set(buildStorefrontCacheKey(ctx, parts, { scope: options.scope }), value, options.ttlMs, options.tags),
279
+ deleteByTags: (tags) => access.deleteByTags(tags),
280
+ }
281
+ }
282
+
283
+ export function ecommerceResolutionCache(container: CacheContainer | null | undefined): EcommerceResolutionCache {
284
+ const access = createTenantScopedAccess({
285
+ cache: resolveCacheService(container),
286
+ tenantId: null,
287
+ baseTags: [],
288
+ })
289
+ return {
290
+ get: (parts) => access.get(buildEcommerceResolutionCacheKey(parts)),
291
+ set: (parts, value, options) =>
292
+ access.set(buildEcommerceResolutionCacheKey(parts), value, options.ttlMs, options.tags),
293
+ deleteByTags: (tags) => access.deleteByTags(tags),
294
+ }
295
+ }
296
+
297
+ export type EcommerceCacheInvalidation = { tenantId: string | null; tags: string[] }
298
+
299
+ /**
300
+ * Event-driven invalidation (§8): evicts `tags` from the tenant-agnostic resolution cache and, when
301
+ * the tenant is known, from that tenant's storefront and buyer-context caches. Tags are tenant
302
+ * scoped by the cache layer, so each scope is cleared inside its own tenant.
303
+ */
304
+ export async function invalidateEcommerceCacheTags(
305
+ container: CacheContainer | null | undefined,
306
+ invalidation: EcommerceCacheInvalidation,
307
+ ): Promise<number> {
308
+ const tags = Array.from(new Set(invalidation.tags.filter((tag) => tag.length > 0)))
309
+ if (tags.length === 0) return 0
310
+ const cache = resolveCacheService(container)
311
+ if (!cache) return 0
312
+ const tenantIds = invalidation.tenantId ? [null, invalidation.tenantId] : [null]
313
+ let deleted = 0
314
+ for (const tenantId of tenantIds) {
315
+ deleted += await createTenantScopedAccess({ cache, tenantId, baseTags: [] }).deleteByTags(tags)
316
+ }
317
+ return deleted
318
+ }
319
+
320
+ export function buyerContextCache(
321
+ container: CacheContainer | null | undefined,
322
+ scope: BuyerContextCacheScope,
323
+ ): BuyerContextCache {
324
+ const access = createTenantScopedAccess({
325
+ cache: resolveCacheService(container),
326
+ tenantId: scope.tenantId,
327
+ baseTags: [ecommerceStoreTag(scope.storeId)],
328
+ })
329
+ return {
330
+ get: (customerUserId) => access.get<BuyerContext>(buildBuyerContextCacheKey(scope.storeId, customerUserId)),
331
+ set: (buyer) =>
332
+ access.set(
333
+ buildBuyerContextCacheKey(scope.storeId, buyer.customerUserId),
334
+ buyer,
335
+ BUYER_CONTEXT_TTL_MS,
336
+ buyerContextTags(buyer, scope.tenantId),
337
+ ),
338
+ deleteByTags: (tags) => access.deleteByTags(tags),
339
+ }
340
+ }
@@ -0,0 +1,31 @@
1
+ import { NextResponse } from 'next/server'
2
+ import { isCrudHttpError } from '@open-mercato/shared/lib/crud/errors'
3
+
4
+ type ConflictRecheck = () => Promise<void>
5
+
6
+ const recheckByRequest = new WeakMap<Request, ConflictRecheck>()
7
+
8
+ export function registerCreateConflictRecheck(request: Request | undefined, recheck: ConflictRecheck): void {
9
+ if (request) recheckByRequest.set(request, recheck)
10
+ }
11
+
12
+ export function clearCreateConflictRecheck(request: Request | undefined): void {
13
+ if (request) recheckByRequest.delete(request)
14
+ }
15
+
16
+ export function withCreateConflictRecheck(
17
+ handler: (request: Request) => Promise<Response>,
18
+ ): (request: Request) => Promise<Response> {
19
+ return async (request: Request): Promise<Response> => {
20
+ const response = await handler(request)
21
+ const recheck = recheckByRequest.get(request)
22
+ recheckByRequest.delete(request)
23
+ if (!recheck || response.status !== 500) return response
24
+ try {
25
+ await recheck()
26
+ } catch (err) {
27
+ if (isCrudHttpError(err) && err.status === 409) return NextResponse.json(err.body, { status: 409 })
28
+ }
29
+ return response
30
+ }
31
+ }