@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,306 @@
1
+ import type { Kysely } from 'kysely'
2
+ import { createLogger } from '@open-mercato/shared/lib/logger'
3
+ import { recordIndexerError } from '@open-mercato/shared/lib/indexers/error-log'
4
+ import { getTelemetryRuntime } from '@open-mercato/shared/lib/telemetry/runtime'
5
+
6
+ const logger = createLogger('query_index').child({ component: 'doc-enrichers' })
7
+
8
+ /**
9
+ * Index-document enrichment — lets the module that owns an entity contribute computed keys to
10
+ * that entity's `entity_indexes.doc` at index time (Storefront Public API §3.3, decision D9).
11
+ *
12
+ * Registration is programmatic (call `registerIndexDocEnricher` from the owning module's `di.ts`);
13
+ * there is no auto-discovered module file for it.
14
+ *
15
+ * Key namespace: every enricher declares the exact keys it writes. A key MUST be a lowercase
16
+ * snake_case identifier (`^[a-z][a-z0-9_]*$`), so it can never collide with `cf:*` custom-field
17
+ * keys or `l10n:*` translation keys, MUST NOT be one of the reserved document keys, and MUST NOT be
18
+ * declared by another enricher of the same entity type. A key that turns out to be a base column
19
+ * of the indexed row is a collision detected at index time: the base value is kept, the
20
+ * contribution is discarded and the error is recorded.
21
+ *
22
+ * Failure semantics (fail closed, detectable): when an enricher throws, or returns nothing for a
23
+ * record, every key it declares is written as JSON `null` on the affected documents. Consumers
24
+ * MUST treat `null` (and an absent key) as "not indexed" — never as "empty". Indexing itself
25
+ * never fails because of an enricher.
26
+ *
27
+ * Enriched keys are filter/projection data: they are excluded from the `search_text` aggregate
28
+ * and from `search_tokens`.
29
+ */
30
+ export type IndexDocEnricherContext = {
31
+ db: Kysely<any>
32
+ entityType: string
33
+ tenantId: string | null
34
+ organizationId: string | null
35
+ }
36
+
37
+ export type IndexDocEnricherRecord = {
38
+ recordId: string
39
+ doc: Readonly<Record<string, unknown>>
40
+ }
41
+
42
+ export type IndexDocEnrichment = Record<string, unknown>
43
+
44
+ export type IndexDocEnricher = {
45
+ id: string
46
+ entityType: string
47
+ keys: readonly string[]
48
+ enrich: (
49
+ records: readonly IndexDocEnricherRecord[],
50
+ ctx: IndexDocEnricherContext,
51
+ ) => Promise<Map<string, IndexDocEnrichment>>
52
+ }
53
+
54
+ export type IndexDocEnrichmentTarget = {
55
+ recordId: string
56
+ doc: Record<string, unknown>
57
+ tenantId: string | null
58
+ organizationId: string | null
59
+ }
60
+
61
+ export const INDEX_DOC_ENRICHER_KEY_PATTERN = /^[a-z][a-z0-9_]*$/
62
+
63
+ export const RESERVED_INDEX_DOC_KEYS: ReadonlySet<string> = new Set([
64
+ 'id',
65
+ 'tenant_id',
66
+ 'organization_id',
67
+ 'created_at',
68
+ 'updated_at',
69
+ 'deleted_at',
70
+ 'search_text',
71
+ ])
72
+
73
+ type RegisteredEnricher = IndexDocEnricher & { keys: string[] }
74
+
75
+ type EnricherRegistryState = {
76
+ byEntityType: Map<string, RegisteredEnricher[]>
77
+ }
78
+
79
+ const GLOBAL_ENRICHER_REGISTRY_KEY = '__openMercatoQueryIndexDocEnrichers__' as const
80
+
81
+ type GlobalEnricherRegistry = typeof globalThis & {
82
+ [GLOBAL_ENRICHER_REGISTRY_KEY]?: EnricherRegistryState
83
+ }
84
+
85
+ function getRegistryState(): EnricherRegistryState {
86
+ const globalRegistry = globalThis as GlobalEnricherRegistry
87
+ if (!globalRegistry[GLOBAL_ENRICHER_REGISTRY_KEY]) {
88
+ globalRegistry[GLOBAL_ENRICHER_REGISTRY_KEY] = { byEntityType: new Map() }
89
+ }
90
+ return globalRegistry[GLOBAL_ENRICHER_REGISTRY_KEY]
91
+ }
92
+
93
+ function validateEnricher(enricher: IndexDocEnricher, existing: readonly RegisteredEnricher[]): string[] {
94
+ if (typeof enricher.id !== 'string' || !enricher.id.trim()) {
95
+ throw new Error('[internal] Index doc enricher requires a non-empty id')
96
+ }
97
+ if (typeof enricher.entityType !== 'string' || !enricher.entityType.includes(':')) {
98
+ throw new Error(`[internal] Index doc enricher "${enricher.id}" requires an entityType in '<module>:<entity>' form`)
99
+ }
100
+ if (typeof enricher.enrich !== 'function') {
101
+ throw new Error(`[internal] Index doc enricher "${enricher.id}" requires an enrich function`)
102
+ }
103
+ const keys = Array.from(new Set(enricher.keys ?? []))
104
+ if (!keys.length) {
105
+ throw new Error(`[internal] Index doc enricher "${enricher.id}" must declare at least one key`)
106
+ }
107
+ for (const key of keys) {
108
+ if (!INDEX_DOC_ENRICHER_KEY_PATTERN.test(key)) {
109
+ throw new Error(`[internal] Index doc enricher "${enricher.id}" key "${key}" must match ${INDEX_DOC_ENRICHER_KEY_PATTERN}`)
110
+ }
111
+ if (RESERVED_INDEX_DOC_KEYS.has(key)) {
112
+ throw new Error(`[internal] Index doc enricher "${enricher.id}" key "${key}" is reserved`)
113
+ }
114
+ const owner = existing.find((entry) => entry.id !== enricher.id && entry.keys.includes(key))
115
+ if (owner) {
116
+ throw new Error(`[internal] Index doc enricher "${enricher.id}" key "${key}" is already contributed by "${owner.id}" for ${enricher.entityType}`)
117
+ }
118
+ }
119
+ return keys
120
+ }
121
+
122
+ /**
123
+ * Registers an enricher. Re-registering the same `id` for the same entity type replaces the
124
+ * previous registration (HMR-safe). Throws on an invalid or colliding key declaration.
125
+ * Returns a function that removes the registration.
126
+ */
127
+ export function registerIndexDocEnricher(enricher: IndexDocEnricher): () => void {
128
+ const state = getRegistryState()
129
+ const existing = state.byEntityType.get(enricher.entityType) ?? []
130
+ const keys = validateEnricher(enricher, existing)
131
+ const registered: RegisteredEnricher = { ...enricher, keys }
132
+ const next = existing.filter((entry) => entry.id !== enricher.id)
133
+ next.push(registered)
134
+ state.byEntityType.set(enricher.entityType, next)
135
+ return () => {
136
+ const current = state.byEntityType.get(enricher.entityType) ?? []
137
+ const remaining = current.filter((entry) => entry !== registered)
138
+ if (remaining.length) state.byEntityType.set(enricher.entityType, remaining)
139
+ else state.byEntityType.delete(enricher.entityType)
140
+ }
141
+ }
142
+
143
+ export function getIndexDocEnrichers(entityType: string): readonly IndexDocEnricher[] {
144
+ return getRegistryState().byEntityType.get(entityType) ?? []
145
+ }
146
+
147
+ export function hasIndexDocEnrichers(entityType: string): boolean {
148
+ return (getRegistryState().byEntityType.get(entityType)?.length ?? 0) > 0
149
+ }
150
+
151
+ export function isIndexDocEnrichedKey(entityType: string | null | undefined, field: string): boolean {
152
+ if (!entityType) return false
153
+ const enrichers = getRegistryState().byEntityType.get(entityType)
154
+ if (!enrichers) return false
155
+ return enrichers.some((entry) => entry.keys.includes(field))
156
+ }
157
+
158
+ export function resetIndexDocEnrichers(): void {
159
+ getRegistryState().byEntityType.clear()
160
+ }
161
+
162
+ function scopeGroupKey(target: IndexDocEnrichmentTarget): string {
163
+ return `${target.tenantId ?? ''}|${target.organizationId ?? ''}`
164
+ }
165
+
166
+ async function reportEnricherFailure(
167
+ db: Kysely<any>,
168
+ input: {
169
+ enricher: RegisteredEnricher
170
+ entityType: string
171
+ tenantId: string | null
172
+ organizationId: string | null
173
+ recordId?: string
174
+ error: unknown
175
+ handler: string
176
+ details?: Record<string, unknown>
177
+ },
178
+ ): Promise<void> {
179
+ logger.error('Index doc enricher failed; contributed keys written as null', {
180
+ enricherId: input.enricher.id,
181
+ entityType: input.entityType,
182
+ tenantId: input.tenantId,
183
+ organizationId: input.organizationId,
184
+ recordId: input.recordId ?? null,
185
+ ...input.details,
186
+ err: input.error,
187
+ })
188
+ getTelemetryRuntime()?.reportError(input.error, {
189
+ module: 'query_index',
190
+ code: 'query_index.doc_enricher_failed',
191
+ })
192
+ await recordIndexerError(
193
+ { db },
194
+ {
195
+ source: 'query_index',
196
+ handler: input.handler,
197
+ error: input.error,
198
+ entityType: input.entityType,
199
+ recordId: input.recordId ?? null,
200
+ tenantId: input.tenantId,
201
+ organizationId: input.organizationId,
202
+ payload: { enricherId: input.enricher.id, ...input.details },
203
+ },
204
+ ).catch(() => undefined)
205
+ }
206
+
207
+ const COLLISION_SAMPLE_SIZE = 10
208
+
209
+ function nullOut(targets: readonly IndexDocEnrichmentTarget[], keys: readonly string[], baseKeys: Map<string, Set<string>>): void {
210
+ for (const target of targets) {
211
+ const ownKeys = baseKeys.get(target.recordId)
212
+ for (const key of keys) {
213
+ if (ownKeys?.has(key)) continue
214
+ target.doc[key] = null
215
+ }
216
+ }
217
+ }
218
+
219
+ /**
220
+ * Runs every enricher registered for `entityType` over a batch of freshly built documents and
221
+ * merges the contributed keys into them in place. One `enrich` call per enricher per
222
+ * tenant/organization scope present in the batch — never per record.
223
+ */
224
+ export async function applyIndexDocEnrichers(
225
+ db: Kysely<any>,
226
+ entityType: string,
227
+ targets: readonly IndexDocEnrichmentTarget[],
228
+ ): Promise<void> {
229
+ const enrichers = getRegistryState().byEntityType.get(entityType)
230
+ if (!enrichers?.length || !targets.length) return
231
+
232
+ const baseKeys = new Map<string, Set<string>>()
233
+ for (const target of targets) baseKeys.set(target.recordId, new Set(Object.keys(target.doc)))
234
+
235
+ const groups = new Map<string, IndexDocEnrichmentTarget[]>()
236
+ for (const target of targets) {
237
+ const key = scopeGroupKey(target)
238
+ const bucket = groups.get(key)
239
+ if (bucket) bucket.push(target)
240
+ else groups.set(key, [target])
241
+ }
242
+
243
+ for (const enricher of [...enrichers]) {
244
+ for (const group of groups.values()) {
245
+ const { tenantId, organizationId } = group[0]
246
+ let result: Map<string, IndexDocEnrichment>
247
+ try {
248
+ const records = group.map((target) => ({ recordId: target.recordId, doc: target.doc }))
249
+ const returned = await enricher.enrich(records, { db, entityType, tenantId, organizationId })
250
+ if (!(returned instanceof Map)) {
251
+ throw new Error(`[internal] Index doc enricher "${enricher.id}" must return a Map keyed by record id`)
252
+ }
253
+ result = returned
254
+ } catch (error) {
255
+ nullOut(group, enricher.keys, baseKeys)
256
+ await reportEnricherFailure(db, {
257
+ enricher, entityType, tenantId, organizationId, error,
258
+ handler: 'query_index:doc-enricher',
259
+ })
260
+ continue
261
+ }
262
+
263
+ const collidingRecordIdsByKey = new Map<string, string[]>()
264
+ for (const target of group) {
265
+ const contribution = result.get(target.recordId)
266
+ const ownKeys = baseKeys.get(target.recordId)
267
+ for (const key of enricher.keys) {
268
+ if (ownKeys?.has(key)) {
269
+ const colliding = collidingRecordIdsByKey.get(key)
270
+ if (colliding) colliding.push(target.recordId)
271
+ else collidingRecordIdsByKey.set(key, [target.recordId])
272
+ continue
273
+ }
274
+ const value = contribution && Object.prototype.hasOwnProperty.call(contribution, key)
275
+ ? contribution[key]
276
+ : null
277
+ target.doc[key] = value === undefined ? null : value
278
+ }
279
+ if (contribution) {
280
+ const undeclared = Object.keys(contribution).filter((key) => !enricher.keys.includes(key))
281
+ if (undeclared.length) {
282
+ logger.warn('Index doc enricher returned undeclared keys; ignored', {
283
+ enricherId: enricher.id,
284
+ entityType,
285
+ recordId: target.recordId,
286
+ keys: undeclared,
287
+ })
288
+ }
289
+ }
290
+ }
291
+ for (const [key, recordIds] of collidingRecordIdsByKey) {
292
+ await reportEnricherFailure(db, {
293
+ enricher, entityType, tenantId, organizationId,
294
+ recordId: recordIds.length === 1 ? recordIds[0] : undefined,
295
+ error: new Error(`[internal] Index doc enricher "${enricher.id}" key "${key}" collides with a base document key of ${entityType} on ${recordIds.length} record(s)`),
296
+ handler: 'query_index:doc-enricher:collision',
297
+ details: {
298
+ collidingKey: key,
299
+ collidingRecordCount: recordIds.length,
300
+ sampleRecordIds: recordIds.slice(0, COLLISION_SAMPLE_SIZE),
301
+ },
302
+ })
303
+ }
304
+ }
305
+ }
306
+ }
@@ -3,6 +3,7 @@ import {
3
3
  resolveSearchConfig,
4
4
  type SearchConfig,
5
5
  } from '@open-mercato/shared/lib/search/config'
6
+ import { isIndexDocEnrichedKey } from './doc-enrichers'
6
7
 
7
8
  export type IndexDocumentScope = {
8
9
  organizationId?: string | null
@@ -67,6 +68,7 @@ function collectAggregateSearchValues(
67
68
  }
68
69
 
69
70
  if (isSearchFieldBlocklisted(field, entityType, config)) return []
71
+ if (isIndexDocEnrichedKey(entityType, field)) return []
70
72
 
71
73
  if (typeof value === 'string') {
72
74
  const trimmed = value.trim()
@@ -4,6 +4,13 @@ import type { EntityId } from '@open-mercato/shared/modules/entities'
4
4
  import type { EntityManager } from '@mikro-orm/postgresql'
5
5
  import { BasicQueryEngine, resolveEntityTableName, resolveRegisteredEntityTableName } from '@open-mercato/shared/lib/query/engine'
6
6
  import { isOrmBackedSystemEntityId } from '@open-mercato/shared/lib/data/engine'
7
+ import {
8
+ buildArrayColumnNoOverlapPredicate,
9
+ buildArrayColumnOverlapPredicate,
10
+ buildJsonbNoOverlapPredicate,
11
+ buildJsonbOverlapPredicate,
12
+ normalizeOverlapValues,
13
+ } from '@open-mercato/shared/lib/query/overlap'
7
14
  import { type Kysely, sql, type RawBuilder } from 'kysely'
8
15
  import type { EventBus } from '@open-mercato/events'
9
16
  import { readCoverageSnapshot, refreshCoverageSnapshot } from './coverage'
@@ -70,7 +77,7 @@ type CustomFieldDefRow = {
70
77
 
71
78
  /** Operators `buildCfFilterExpression` compiles; anything else yields no predicate. */
72
79
  const CF_FILTER_SUPPORTED_OPS = new Set<FilterOp>([
73
- 'eq', 'ne', 'in', 'nin', 'like', 'ilike', 'exists', 'gt', 'gte', 'lt', 'lte',
80
+ 'eq', 'ne', 'in', 'nin', 'like', 'ilike', 'exists', 'gt', 'gte', 'lt', 'lte', 'overlap', 'noverlap',
74
81
  ])
75
82
 
76
83
  /** Custom field kinds stored numerically (`custom_field_values.value_int`/`value_float`) — sort numerically, not as text (#5674). */
@@ -1007,6 +1014,8 @@ export class HybridQueryEngine implements QueryEngine {
1007
1014
  case 'like': return target.where(column, 'like', value as any)
1008
1015
  case 'ilike': return target.where(column, 'ilike', value as any)
1009
1016
  case 'exists': return value ? target.where(column, 'is not', null) : target.where(column, 'is', null)
1017
+ case 'overlap': return target.where(buildArrayColumnOverlapPredicate(column, value))
1018
+ case 'noverlap': return target.where(buildArrayColumnNoOverlapPredicate(column, value))
1010
1019
  default: return target
1011
1020
  }
1012
1021
  }
@@ -1710,6 +1719,18 @@ export class HybridQueryEngine implements QueryEngine {
1710
1719
  ])
1711
1720
  )
1712
1721
  }
1722
+ case 'noverlap':
1723
+ return sql<boolean>`false`
1724
+ case 'overlap': {
1725
+ const values = normalizeOverlapValues(value)
1726
+ if (!values.length) return sql<boolean>`false`
1727
+ return eb.or(
1728
+ values.flatMap((val) => [
1729
+ sql<boolean>`${textExpr} = ${val}`,
1730
+ arrContains(val),
1731
+ ])
1732
+ )
1733
+ }
1713
1734
  case 'nin': {
1714
1735
  const values = this.toArray(value)
1715
1736
  return sql<boolean>`${textExpr} not in (${sql.join(values.map((v) => sql`${v}`), sql`, `)})`
@@ -1886,6 +1907,18 @@ export class HybridQueryEngine implements QueryEngine {
1886
1907
  ])
1887
1908
  ))
1888
1909
  }
1910
+ case 'noverlap':
1911
+ return q.where(sql<boolean>`false`)
1912
+ case 'overlap': {
1913
+ const vals = normalizeOverlapValues(value)
1914
+ if (!vals.length) return q.where(sql<boolean>`false`)
1915
+ return q.where((eb: any) => eb.or(
1916
+ vals.flatMap((val) => [
1917
+ sql<boolean>`${textExpr} = ${val}`,
1918
+ arrContains(val),
1919
+ ])
1920
+ ))
1921
+ }
1889
1922
  case 'nin': {
1890
1923
  const vals = this.toArray(value)
1891
1924
  return q.where(sql<boolean>`${textExpr} not in (${sql.join(vals.map((v) => sql`${v}`), sql`, `)})`)
@@ -1954,6 +1987,10 @@ export class HybridQueryEngine implements QueryEngine {
1954
1987
  const vals = this.toArray(value)
1955
1988
  return q.where(sql<boolean>`${textExpr} in (${sql.join(vals.map((v) => sql`${v}`), sql`, `)})`)
1956
1989
  }
1990
+ case 'overlap':
1991
+ return q.where(buildJsonbOverlapPredicate(`${alias}.doc`, key, value))
1992
+ case 'noverlap':
1993
+ return q.where(buildJsonbNoOverlapPredicate(`${alias}.doc`, key, value))
1957
1994
  case 'nin': {
1958
1995
  const vals = this.toArray(value)
1959
1996
  return q.where(sql<boolean>`${textExpr} not in (${sql.join(vals.map((v) => sql`${v}`), sql`, `)})`)
@@ -2071,6 +2108,8 @@ export class HybridQueryEngine implements QueryEngine {
2071
2108
  case 'like': return eb(column, 'like', value)
2072
2109
  case 'ilike': return eb(column, 'ilike', value)
2073
2110
  case 'exists': return eb(column, value ? 'is not' : 'is', null)
2111
+ case 'overlap': return buildArrayColumnOverlapPredicate(column, value)
2112
+ case 'noverlap': return buildArrayColumnNoOverlapPredicate(column, value)
2074
2113
  default: return sql<boolean>`true`
2075
2114
  }
2076
2115
  }
@@ -2102,6 +2141,10 @@ export class HybridQueryEngine implements QueryEngine {
2102
2141
  const vals = this.toArray(value)
2103
2142
  return sql<boolean>`${textExpr} in (${sql.join(vals.map((v) => sql`${v}`), sql`, `)})`
2104
2143
  }
2144
+ case 'overlap':
2145
+ return buildJsonbOverlapPredicate(`${alias}.doc`, key, value)
2146
+ case 'noverlap':
2147
+ return buildJsonbNoOverlapPredicate(`${alias}.doc`, key, value)
2105
2148
  case 'nin': {
2106
2149
  const vals = this.toArray(value)
2107
2150
  return sql<boolean>`${textExpr} not in (${sql.join(vals.map((v) => sql`${v}`), sql`, `)})`
@@ -2158,6 +2201,14 @@ export class HybridQueryEngine implements QueryEngine {
2158
2201
  })
2159
2202
  continue
2160
2203
  }
2204
+ if (filter.op === 'overlap') {
2205
+ next = next.where(buildJsonbOverlapPredicate(`${alias}.doc`, String(filter.field), filter.value))
2206
+ continue
2207
+ }
2208
+ if (filter.op === 'noverlap') {
2209
+ next = next.where(buildJsonbNoOverlapPredicate(`${alias}.doc`, String(filter.field), filter.value))
2210
+ continue
2211
+ }
2161
2212
  // Unknown field → filter on doc JSON text
2162
2213
  const docExpr = sql<string | null>`(${sql.ref(alias + '.doc')} ->> ${String(filter.field)})`
2163
2214
  next = this.applyColumnFilter(next, docExpr, filter, {
@@ -2748,6 +2799,8 @@ export class HybridQueryEngine implements QueryEngine {
2748
2799
  case '$like': add(field, 'like', opVal); break
2749
2800
  case '$ilike': add(field, 'ilike', opVal); break
2750
2801
  case '$exists': add(field, 'exists', opVal); break
2802
+ case '$overlap': add(field, 'overlap', opVal); break
2803
+ case '$noverlap': add(field, 'noverlap', opVal); break
2751
2804
  }
2752
2805
  }
2753
2806
  } else {
@@ -2889,6 +2942,10 @@ export class HybridQueryEngine implements QueryEngine {
2889
2942
  return q.where(col, 'ilike', filter.value as any)
2890
2943
  case 'exists':
2891
2944
  return filter.value ? q.where(col, 'is not', null) : q.where(col, 'is', null)
2945
+ case 'overlap':
2946
+ return q.where(buildArrayColumnOverlapPredicate(col, filter.value))
2947
+ case 'noverlap':
2948
+ return q.where(buildArrayColumnNoOverlapPredicate(col, filter.value))
2892
2949
  default:
2893
2950
  return q
2894
2951
  }
@@ -13,6 +13,7 @@ import { createLogger } from '@open-mercato/shared/lib/logger'
13
13
  import { resolveSearchConfig } from '@open-mercato/shared/lib/search/config'
14
14
  import { replaceSearchTokensForRecord, deleteSearchTokensForRecord } from './search-tokens'
15
15
  import { attachAggregateSearchField, rebuildAggregateSearchField } from './document'
16
+ import { applyIndexDocEnrichers } from './doc-enrichers'
16
17
 
17
18
  const logger = createLogger('query_index').child({ component: 'indexer' })
18
19
 
@@ -198,6 +199,13 @@ export async function buildIndexDoc(em: EntityManager, params: BuildDocParams):
198
199
  }
199
200
  } catch {}
200
201
 
202
+ await applyIndexDocEnrichers(db, params.entityType, [{
203
+ recordId: String(params.recordId),
204
+ doc,
205
+ tenantId: params.tenantId ?? null,
206
+ organizationId: params.organizationId ?? null,
207
+ }])
208
+
201
209
  // Kept outside the guard below: a failure while building the aggregate search field is a
202
210
  // bug in the aggregation or its configuration, and must surface instead of being mistaken
203
211
  // for an encryption failure and silently skipping encryption.
@@ -10,6 +10,7 @@ import { looksLikeEncryptedPayload } from '@open-mercato/shared/lib/encryption/a
10
10
  import { createKmsService, resolveEncryptionMode, type KmsService } from '@open-mercato/shared/lib/encryption/kms'
11
11
  import { parseBooleanToken } from '@open-mercato/shared/lib/boolean'
12
12
  import { createLogger } from '@open-mercato/shared/lib/logger'
13
+ import { isIndexDocEnrichedKey } from './doc-enrichers'
13
14
 
14
15
  const logger = createLogger('query_index').child({ component: 'search-tokens' })
15
16
 
@@ -166,6 +167,7 @@ function shouldIndexField(
166
167
  if (lower.endsWith('_at')) return false
167
168
  if (['created_at', 'updated_at', 'deleted_at', 'tenant_id', 'organization_id'].includes(lower)) return false
168
169
  if (isSearchFieldBlocklisted(field, entityType, config)) return false
170
+ if (isIndexDocEnrichedKey(entityType, field)) return false
169
171
  return collectTextValues(value).some((text) => text.length > 0)
170
172
  }
171
173
 
@@ -0,0 +1,42 @@
1
+ import { Migration } from '@mikro-orm/migrations';
2
+
3
+ // GIN expression index serving the storefront assortment predicate (Storefront Public API
4
+ // §3.3, D9): `(doc -> 'scope_keys') ?| $n::text[]` on catalog:catalog_product index documents,
5
+ // whose `scope_keys` key the catalog contributes through its doc enricher
6
+ // (catalog/lib/productScopeKeys.ts).
7
+ //
8
+ // Ownership: query_index owns the migration (as the spec's §3.3 table assigns it) because it
9
+ // owns `entity_indexes`; a catalog migration cannot do it, since modules migrate in
10
+ // alphabetical order and on a fresh database catalog's migrations run before query_index
11
+ // creates the table. MANUAL DDL: a partial expression index keyed on another module's entity
12
+ // type is not modelled on the EntityIndexRow entity, so `.snapshot-open-mercato.json` does not
13
+ // record it; `yarn db:generate` diffs entity metadata against the snapshot, so it never emits a
14
+ // drop for it (same approach as the Omnibus trigger DDL in catalog's
15
+ // Migration20261005150824_catalog).
16
+ //
17
+ // The expression must be spelled exactly as the query engine emits it
18
+ // (`jsonbDocKeyExpression` in @open-mercato/shared/lib/query/overlap emits the key as a
19
+ // literal) or PostgreSQL silently falls back to a sequential scan; the catalog
20
+ // product-scope-keys-index test pins both sides. Default `jsonb_ops` (not `jsonb_path_ops`),
21
+ // because only `jsonb_ops` supports `?|`.
22
+ //
23
+ // entity_indexes is high-churn, so the index is built CONCURRENTLY, which cannot run inside a
24
+ // transaction (isTransactional() => false; the runner applies migrations one-by-one). Drop
25
+ // first so retrying an interrupted build removes PostgreSQL's INVALID index stub. Same shape as
26
+ // Migration20260731105052_query_index.
27
+ export class Migration20261005201500_query_index extends Migration {
28
+
29
+ override isTransactional(): boolean {
30
+ return false;
31
+ }
32
+
33
+ override up(): void | Promise<void> {
34
+ this.addSql(`drop index concurrently if exists "entity_indexes_catalog_product_scope_keys_gin_idx";`);
35
+ this.addSql(`create index concurrently "entity_indexes_catalog_product_scope_keys_gin_idx" on "entity_indexes" using gin (("doc" -> 'scope_keys')) where "entity_type" = 'catalog:catalog_product';`);
36
+ }
37
+
38
+ override down(): void | Promise<void> {
39
+ this.addSql(`drop index concurrently if exists "entity_indexes_catalog_product_scope_keys_gin_idx";`);
40
+ }
41
+
42
+ }
@@ -190,8 +190,8 @@ export function TranslationManager({
190
190
  })
191
191
 
192
192
  const fieldList = React.useMemo(
193
- () => resolveFieldList(entityType, propTranslatableFields, fieldDefs as Array<{ key: string; kind: string; label?: string }>),
194
- [entityType, propTranslatableFields, fieldDefs],
193
+ () => resolveFieldList(entityType, propTranslatableFields, fieldDefs as Array<{ key: string; kind: string; label?: string }>, baseValues),
194
+ [entityType, propTranslatableFields, fieldDefs, baseValues],
195
195
  )
196
196
 
197
197
  const {
@@ -440,7 +440,7 @@ export function TranslationManager({
440
440
  </thead>
441
441
  <tbody>
442
442
  {fieldList.map((field) => {
443
- const baseVal = getBaseValue(field.key)
443
+ const baseVal = field.baseValue ?? getBaseValue(field.key)
444
444
  const translatedVal = localeTranslations[field.key] ?? ''
445
445
 
446
446
  return (
@@ -4,7 +4,7 @@ export const translationBodySchema = z
4
4
  .record(
5
5
  z.string().trim().min(2).max(10),
6
6
  z.record(
7
- z.string().trim().min(1).max(100),
7
+ z.string().trim().min(1).max(400),
8
8
  z.union([z.string().max(10000), z.null()]),
9
9
  ),
10
10
  )
@@ -23,3 +23,50 @@ export async function batchLoadTranslations(
23
23
  }
24
24
  return map
25
25
  }
26
+
27
+ type EntityTranslationsTable = {
28
+ entity_translations: {
29
+ entity_type: string
30
+ entity_id: string
31
+ translations: Record<string, Record<string, unknown>> | null
32
+ tenant_id: string | null
33
+ organization_id: string | null
34
+ }
35
+ }
36
+
37
+ /**
38
+ * Multi-entity-type variant of `batchLoadTranslations`: every request is answered by one query,
39
+ * keyed by entity type and then entity id. Organization matching is exact, as in the single-type helper.
40
+ */
41
+ export async function batchLoadTranslationsMany(
42
+ db: Kysely<EntityTranslationsTable>,
43
+ requests: Array<{ entityType: string; entityIds: string[] }>,
44
+ scope: { tenantId?: string | null; organizationId?: string | null },
45
+ ): Promise<Map<string, Map<string, Record<string, Record<string, unknown>>>>> {
46
+ const result = new Map<string, Map<string, Record<string, Record<string, unknown>>>>()
47
+ const wanted = new Map<string, Set<string>>()
48
+ for (const request of requests) {
49
+ if (!result.has(request.entityType)) result.set(request.entityType, new Map())
50
+ const ids = wanted.get(request.entityType) ?? new Set<string>()
51
+ for (const id of request.entityIds) ids.add(id)
52
+ wanted.set(request.entityType, ids)
53
+ }
54
+ const entityTypes = Array.from(wanted.entries()).filter(([, ids]) => ids.size > 0).map(([entityType]) => entityType)
55
+ if (!entityTypes.length) return result
56
+ const entityIds = Array.from(new Set(entityTypes.flatMap((entityType) => Array.from(wanted.get(entityType) ?? []))))
57
+
58
+ const rows = await db
59
+ .selectFrom('entity_translations')
60
+ .select(['entity_type', 'entity_id', 'translations'])
61
+ .where('entity_type', 'in', entityTypes)
62
+ .where('entity_id', 'in', entityIds)
63
+ .where(sql<boolean>`tenant_id is not distinct from ${scope.tenantId ?? null}`)
64
+ .where(sql<boolean>`organization_id is not distinct from ${scope.organizationId ?? null}`)
65
+ .execute()
66
+
67
+ for (const row of rows) {
68
+ if (!wanted.get(row.entity_type)?.has(row.entity_id)) continue
69
+ result.get(row.entity_type)?.set(row.entity_id, row.translations ?? {})
70
+ }
71
+ return result
72
+ }
@@ -1,3 +1,5 @@
1
+ import { getTranslatableEntityListPath } from '@open-mercato/shared/lib/localization/translatable-fields'
2
+
1
3
  export function formatFieldLabel(field: string): string {
2
4
  if (field.includes('.')) {
3
5
  const parts = field.split('.')
@@ -45,6 +47,8 @@ function pluralize(word: string): string {
45
47
  export function buildEntityListUrl(entityType: string): string | null {
46
48
  const [module, entity] = entityType.split(':')
47
49
  if (!module || !entity) return null
50
+ const registeredPath = getTranslatableEntityListPath(entityType)
51
+ if (registeredPath) return registeredPath
48
52
  const prefix = `${module}_`
49
53
  const base = entity.startsWith(prefix) ? entity.slice(prefix.length) : entity
50
54
  const resource = pluralize(base).replace(/_/g, '-')
@@ -1,9 +1,9 @@
1
- import { getTranslatableFields } from '@open-mercato/shared/lib/localization/translatable-fields'
1
+ import { getTranslatableFieldExpander, getTranslatableFields } from '@open-mercato/shared/lib/localization/translatable-fields'
2
2
  import { getEntityFields } from '#generated/entity-fields-registry'
3
3
  import { isTranslatableField } from './translatable-fields'
4
4
  import { formatFieldLabel } from './helpers'
5
5
 
6
- export type ResolvedField = { key: string; label: string; multiline: boolean }
6
+ export type ResolvedField = { key: string; label: string; multiline: boolean; baseValue?: string }
7
7
 
8
8
  function isMultiline(key: string): boolean {
9
9
  return key === 'description' || key.includes('description') || key.includes('content')
@@ -13,6 +13,7 @@ export function resolveFieldList(
13
13
  entityType: string,
14
14
  explicitFields: string[] | undefined,
15
15
  customFieldDefs: Array<{ key: string; kind: string; label?: string }>,
16
+ baseValues?: Record<string, unknown> | null,
16
17
  ): ResolvedField[] {
17
18
  if (explicitFields?.length) {
18
19
  return explicitFields.map((key) => ({
@@ -56,6 +57,19 @@ export function resolveFieldList(
56
57
  }
57
58
  }
58
59
 
60
+ const expander = getTranslatableFieldExpander(entityType)
61
+ if (expander && baseValues) {
62
+ for (const expansion of expander(baseValues)) {
63
+ if (fields.some((f) => f.key === expansion.key)) continue
64
+ fields.push({
65
+ key: expansion.key,
66
+ label: expansion.label ?? formatFieldLabel(expansion.key),
67
+ multiline: isMultiline(expansion.key),
68
+ ...(expansion.baseValue !== undefined ? { baseValue: expansion.baseValue } : {}),
69
+ })
70
+ }
71
+ }
72
+
59
73
  if (hasExplicitList) return fields
60
74
 
61
75
  for (const def of customFieldDefs) {