@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,393 @@
1
+ var _a, _b;
2
+ import { parseBooleanWithDefault } from "@open-mercato/shared/lib/boolean";
3
+ import { createLogger } from "@open-mercato/shared/lib/logger";
4
+ import { getTelemetryRuntime } from "@open-mercato/shared/lib/telemetry/runtime";
5
+ import { secretEqual } from "@open-mercato/core/modules/customer_accounts/lib/secretCompare";
6
+ import {
7
+ ECOMMERCE_STORE_SLUG_PATTERN,
8
+ ecommercePriceSortFallbackSchema,
9
+ ecommerceStoreSettingsSchema,
10
+ ecommerceStoreStatusSchema
11
+ } from "../data/validators.js";
12
+ import { emitEcommerceEvent } from "../events.js";
13
+ import {
14
+ ecommerceDomainMappingTag,
15
+ ecommerceDomainTag,
16
+ ecommerceResolutionCache,
17
+ ecommerceStoreTag
18
+ } from "./cacheKeys.js";
19
+ import { resolveStoreLocale } from "./storeLocale.js";
20
+ const DEV_STORE_SLUG_ENV = "OM_ECOMMERCE_DEV_STORE_SLUG";
21
+ const STORE_RESOLUTION_TTL_MS = 3e5;
22
+ const STORE_MISCONFIGURED_THROTTLE_MS = 36e5;
23
+ const STOREFRONT_RESOLUTION_ERROR_MARKER = /* @__PURE__ */ Symbol.for("@open-mercato/ecommerce/StorefrontResolutionError");
24
+ class StorefrontResolutionError extends (_b = Error, _a = STOREFRONT_RESOLUTION_ERROR_MARKER, _b) {
25
+ constructor(status, code) {
26
+ super(`[internal] storefront store resolution failed: ${code}`);
27
+ this[_a] = true;
28
+ this.name = "StorefrontResolutionError";
29
+ this.status = status;
30
+ this.code = code;
31
+ }
32
+ }
33
+ function isStorefrontResolutionError(error) {
34
+ return !!error && typeof error === "object" && error[STOREFRONT_RESOLUTION_ERROR_MARKER] === true;
35
+ }
36
+ const STORE_COLUMNS = [
37
+ "s.id as store_id",
38
+ "s.tenant_id as store_tenant_id",
39
+ "s.organization_id as store_organization_id",
40
+ "s.code as store_code",
41
+ "s.name as store_name",
42
+ "s.slug as store_slug",
43
+ "s.status as store_status",
44
+ "s.default_locale as store_default_locale",
45
+ "s.supported_locales as store_supported_locales",
46
+ "s.default_currency_code as store_default_currency_code",
47
+ "s.settings as store_settings",
48
+ "c.id as channel_binding_id",
49
+ "c.sales_channel_id as channel_sales_channel_id",
50
+ "c.price_kind_id as channel_price_kind_id",
51
+ "c.assortment_scope as channel_assortment_scope",
52
+ "c.price_sort_fallback as channel_price_sort_fallback",
53
+ "c.require_authentication as channel_require_authentication"
54
+ ];
55
+ const FORCE_HOST_HEADER = "x-force-host";
56
+ const FORCE_HOST_SECRET_HEADER = "x-force-host-secret";
57
+ const SLUG_MAX_LENGTH = 120;
58
+ const logger = createLogger("ecommerce").child({ component: "store-context" });
59
+ const WARN_NOT_FOUND_REASONS = /* @__PURE__ */ new Set([
60
+ "domain_routing_unavailable",
61
+ "domain_binding_not_found",
62
+ "slug_ambiguous"
63
+ ]);
64
+ function storeNotFound(reason, details) {
65
+ const meta = { reason, ...details };
66
+ if (WARN_NOT_FOUND_REASONS.has(reason)) {
67
+ logger.warn("Storefront store not resolved", meta);
68
+ } else {
69
+ logger.debug("Storefront store not resolved", meta);
70
+ }
71
+ return new StorefrontResolutionError(404, "store_not_found");
72
+ }
73
+ function tryResolve(container, name) {
74
+ try {
75
+ const resolved = container.resolve(name);
76
+ return resolved ?? null;
77
+ } catch {
78
+ return null;
79
+ }
80
+ }
81
+ function resolveEntityManager(container) {
82
+ const em = tryResolve(container, "em");
83
+ if (!em) throw new Error("[internal] ecommerce store resolution requires the DI entity manager");
84
+ return em;
85
+ }
86
+ function isDevStoreSlugEnabled() {
87
+ return parseBooleanWithDefault(process.env[DEV_STORE_SLUG_ENV], false);
88
+ }
89
+ function normalizeRequestHost(raw) {
90
+ if (typeof raw !== "string") return null;
91
+ const first = raw.split(",")[0]?.trim().toLowerCase() ?? "";
92
+ if (!first.length) return null;
93
+ const withoutPort = first.startsWith("[") ? first.replace(/^(\[[^\]]*\])(?::\d+)?$/, "$1") : first.replace(/:\d+$/, "");
94
+ const withoutTrailingDot = withoutPort.replace(/\.+$/, "");
95
+ return withoutTrailingDot.length ? withoutTrailingDot : null;
96
+ }
97
+ function normalizeRequestPathname(pathname) {
98
+ if (typeof pathname !== "string" || !pathname.length) return "/";
99
+ const lowered = pathname.toLowerCase();
100
+ const withLeadingSlash = lowered.startsWith("/") ? lowered : `/${lowered}`;
101
+ const withoutTrailingSlash = withLeadingSlash.replace(/\/+$/, "");
102
+ return withoutTrailingSlash.length ? withoutTrailingSlash : "/";
103
+ }
104
+ function pathPrefixMatches(pathPrefix, pathname) {
105
+ if (!pathPrefix) return true;
106
+ const normalizedPrefix = normalizeRequestPathname(pathPrefix);
107
+ if (normalizedPrefix === "/") return true;
108
+ return pathname === normalizedPrefix || pathname.startsWith(`${normalizedPrefix}/`);
109
+ }
110
+ function selectLongestPrefixMatch(candidates, pathname) {
111
+ let best = null;
112
+ let bestLength = -1;
113
+ for (const candidate of candidates) {
114
+ const pathPrefix = candidate.binding?.pathPrefix ?? null;
115
+ if (!pathPrefixMatches(pathPrefix, pathname)) continue;
116
+ const length = pathPrefix ? normalizeRequestPathname(pathPrefix).length : 0;
117
+ if (length > bestLength) {
118
+ best = candidate;
119
+ bestLength = length;
120
+ }
121
+ }
122
+ return best;
123
+ }
124
+ function readForcedHost(request) {
125
+ if (process.env.NODE_ENV !== "test") return null;
126
+ const expected = process.env.FORCE_HOST_SECRET;
127
+ if (!expected) return null;
128
+ if (!secretEqual(request.headers.get(FORCE_HOST_SECRET_HEADER), expected)) return null;
129
+ const forced = request.headers.get(FORCE_HOST_HEADER);
130
+ return forced && forced.length > 0 ? forced : null;
131
+ }
132
+ function readLocaleCandidates(request, url) {
133
+ return {
134
+ queryLocale: url.searchParams.get("locale"),
135
+ headerLocale: request.headers.get("x-locale"),
136
+ acceptLanguage: request.headers.get("accept-language")
137
+ };
138
+ }
139
+ function parseJsonColumn(value) {
140
+ if (typeof value !== "string") return value;
141
+ try {
142
+ return JSON.parse(value);
143
+ } catch {
144
+ return null;
145
+ }
146
+ }
147
+ function isRecord(value) {
148
+ return typeof value === "object" && value !== null && !Array.isArray(value);
149
+ }
150
+ function parseSettings(raw, storeId) {
151
+ const parsed = ecommerceStoreSettingsSchema.safeParse(parseJsonColumn(raw) ?? {});
152
+ if (parsed.success) return parsed.data;
153
+ logger.warn("Store settings failed validation; serving defaults", { storeId });
154
+ return ecommerceStoreSettingsSchema.parse({});
155
+ }
156
+ function parseSupportedLocales(raw, defaultLocale) {
157
+ const value = parseJsonColumn(raw);
158
+ const locales = Array.isArray(value) ? value.filter((locale) => typeof locale === "string" && locale.length > 0) : [];
159
+ return locales.includes(defaultLocale) ? locales : [defaultLocale, ...locales];
160
+ }
161
+ function parseStatus(raw) {
162
+ const parsed = ecommerceStoreStatusSchema.safeParse(raw);
163
+ return parsed.success ? parsed.data : "draft";
164
+ }
165
+ function parsePriceSortFallback(raw) {
166
+ const parsed = ecommercePriceSortFallbackSchema.safeParse(raw);
167
+ return parsed.success ? parsed.data : "approximate";
168
+ }
169
+ function parseAssortmentScope(raw) {
170
+ const value = parseJsonColumn(raw);
171
+ return isRecord(value) ? value : null;
172
+ }
173
+ function toCandidate(row) {
174
+ const store = {
175
+ id: row.store_id,
176
+ tenantId: row.store_tenant_id,
177
+ organizationId: row.store_organization_id,
178
+ code: row.store_code,
179
+ name: row.store_name,
180
+ slug: row.store_slug,
181
+ status: parseStatus(row.store_status),
182
+ defaultLocale: row.store_default_locale,
183
+ supportedLocales: parseSupportedLocales(row.store_supported_locales, row.store_default_locale),
184
+ defaultCurrencyCode: row.store_default_currency_code,
185
+ settings: parseSettings(row.store_settings, row.store_id)
186
+ };
187
+ const channel = row.channel_binding_id && row.channel_sales_channel_id ? {
188
+ channelBindingId: row.channel_binding_id,
189
+ salesChannelId: row.channel_sales_channel_id,
190
+ priceKindId: row.channel_price_kind_id ?? null,
191
+ priceSortFallback: parsePriceSortFallback(row.channel_price_sort_fallback),
192
+ assortmentScope: parseAssortmentScope(row.channel_assortment_scope),
193
+ requireAuthentication: row.channel_require_authentication === true
194
+ } : null;
195
+ const binding = row.binding_id ? { id: row.binding_id, pathPrefix: row.binding_path_prefix ?? null, isPrimary: row.binding_is_primary === true } : null;
196
+ return { store, channel, binding };
197
+ }
198
+ async function queryDomainCandidates(em, mapping) {
199
+ const db = em.getKysely();
200
+ return db.selectFrom("ecommerce_store_domain_bindings as b").innerJoin(
201
+ "ecommerce_stores as s",
202
+ (join) => join.onRef("s.id", "=", "b.store_id").onRef("s.tenant_id", "=", "b.tenant_id").onRef("s.organization_id", "=", "b.organization_id").on("s.deleted_at", "is", null)
203
+ ).leftJoin(
204
+ "ecommerce_store_channel_bindings as c",
205
+ (join) => join.onRef("c.store_id", "=", "s.id").onRef("c.tenant_id", "=", "s.tenant_id").onRef("c.organization_id", "=", "s.organization_id").on("c.is_default", "=", true).on("c.deleted_at", "is", null)
206
+ ).select([
207
+ ...STORE_COLUMNS,
208
+ "b.id as binding_id",
209
+ "b.path_prefix as binding_path_prefix",
210
+ "b.is_primary as binding_is_primary"
211
+ ]).where("b.domain_mapping_id", "=", mapping.domainMappingId).where("b.tenant_id", "=", mapping.tenantId).where("b.organization_id", "=", mapping.organizationId).where("b.deleted_at", "is", null).execute();
212
+ }
213
+ async function querySlugCandidates(em, slug, tenantId) {
214
+ const db = em.getKysely();
215
+ let query = db.selectFrom("ecommerce_stores as s").leftJoin(
216
+ "ecommerce_store_channel_bindings as c",
217
+ (join) => join.onRef("c.store_id", "=", "s.id").onRef("c.tenant_id", "=", "s.tenant_id").onRef("c.organization_id", "=", "s.organization_id").on("c.is_default", "=", true).on("c.deleted_at", "is", null)
218
+ ).select([...STORE_COLUMNS]).where("s.slug", "=", slug).where("s.deleted_at", "is", null);
219
+ if (tenantId) query = query.where("s.tenant_id", "=", tenantId);
220
+ return query.limit(2).execute();
221
+ }
222
+ async function loadDomainCandidates(container, mapping) {
223
+ const cache = ecommerceResolutionCache(container);
224
+ const key = ["domain-mapping", mapping.domainMappingId];
225
+ const cached = await cache.get(key);
226
+ if (cached && Array.isArray(cached.candidates)) return cached.candidates;
227
+ const rows = await queryDomainCandidates(resolveEntityManager(container), mapping);
228
+ const candidates = rows.map(toCandidate);
229
+ const tags = [
230
+ ecommerceDomainMappingTag(mapping.domainMappingId),
231
+ ecommerceDomainTag(mapping.hostname),
232
+ ...candidates.map((candidate) => ecommerceStoreTag(candidate.store.id))
233
+ ];
234
+ await cache.set(
235
+ key,
236
+ { candidates },
237
+ { ttlMs: STORE_RESOLUTION_TTL_MS, tags: Array.from(new Set(tags)) }
238
+ );
239
+ return candidates;
240
+ }
241
+ async function loadSlugCandidates(container, slug, tenantId) {
242
+ const cache = ecommerceResolutionCache(container);
243
+ const key = ["slug", tenantId ?? "*", slug];
244
+ const cached = await cache.get(key);
245
+ if (cached && Array.isArray(cached.candidates)) return cached.candidates;
246
+ const rows = await querySlugCandidates(resolveEntityManager(container), slug, tenantId);
247
+ const candidates = rows.map(toCandidate);
248
+ if (candidates.length > 0) {
249
+ await cache.set(
250
+ key,
251
+ { candidates },
252
+ {
253
+ ttlMs: STORE_RESOLUTION_TTL_MS,
254
+ tags: candidates.map((candidate) => ecommerceStoreTag(candidate.store.id))
255
+ }
256
+ );
257
+ }
258
+ return candidates;
259
+ }
260
+ async function reportMisconfiguredStore(container, store) {
261
+ const cache = ecommerceResolutionCache(container);
262
+ const key = ["misconfigured", "channel-binding-missing", store.id];
263
+ const alreadyReported = await cache.get(key);
264
+ if (alreadyReported) return;
265
+ await cache.set(key, { reportedAt: (/* @__PURE__ */ new Date()).toISOString() }, { ttlMs: STORE_MISCONFIGURED_THROTTLE_MS });
266
+ logger.warn("Store has no default channel binding; answering 503", {
267
+ storeId: store.id,
268
+ tenantId: store.tenantId,
269
+ organizationId: store.organizationId
270
+ });
271
+ try {
272
+ await emitEcommerceEvent(
273
+ "ecommerce.store.misconfigured",
274
+ {
275
+ id: store.id,
276
+ storeId: store.id,
277
+ tenantId: store.tenantId,
278
+ organizationId: store.organizationId,
279
+ reason: "channel_binding_missing"
280
+ },
281
+ { persistent: true, tenantId: store.tenantId, organizationId: store.organizationId }
282
+ );
283
+ } catch (error) {
284
+ logger.warn("Failed to emit store misconfiguration event", { storeId: store.id, err: error });
285
+ getTelemetryRuntime()?.reportError(error, {
286
+ module: "ecommerce",
287
+ code: "ecommerce.store_misconfigured_emit_failed",
288
+ attributes: { storeId: store.id }
289
+ });
290
+ }
291
+ }
292
+ async function finalizeCandidate(container, candidate, source, domain, localeCandidates) {
293
+ const { store } = candidate;
294
+ if (store.status === "draft") {
295
+ if (isDevStoreSlugEnabled()) throw new StorefrontResolutionError(403, "store_draft");
296
+ throw storeNotFound("store_draft_hidden", { source, storeId: store.id });
297
+ }
298
+ if (store.status === "archived") throw new StorefrontResolutionError(410, "store_archived");
299
+ if (!candidate.channel) {
300
+ await reportMisconfiguredStore(container, store);
301
+ throw new StorefrontResolutionError(503, "store_misconfigured");
302
+ }
303
+ const locale = resolveStoreLocale(store, localeCandidates);
304
+ return {
305
+ source,
306
+ store: {
307
+ id: store.id,
308
+ code: store.code,
309
+ name: store.name,
310
+ slug: store.slug,
311
+ status: "active",
312
+ defaultLocale: store.defaultLocale,
313
+ supportedLocales: store.supportedLocales,
314
+ defaultCurrencyCode: store.defaultCurrencyCode,
315
+ settings: store.settings
316
+ },
317
+ tenantId: store.tenantId,
318
+ organizationId: store.organizationId,
319
+ channel: candidate.channel,
320
+ domain,
321
+ effectiveLocale: locale.effectiveLocale,
322
+ requestedLocale: locale.requestedLocale,
323
+ currencyCode: store.defaultCurrencyCode
324
+ };
325
+ }
326
+ async function resolveSlugWithCandidates(container, rawSlug, tenantId, localeCandidates) {
327
+ if (!isDevStoreSlugEnabled()) throw new StorefrontResolutionError(400, "store_slug_not_allowed");
328
+ const slug = rawSlug.trim().toLowerCase();
329
+ if (!slug.length || slug.length > SLUG_MAX_LENGTH || !ECOMMERCE_STORE_SLUG_PATTERN.test(slug)) {
330
+ throw storeNotFound("slug_invalid", {});
331
+ }
332
+ const candidates = await loadSlugCandidates(container, slug, tenantId);
333
+ if (candidates.length === 0) throw storeNotFound("slug_not_found", { slug });
334
+ if (candidates.length > 1) throw storeNotFound("slug_ambiguous", { slug });
335
+ return finalizeCandidate(container, candidates[0], "slug", null, localeCandidates);
336
+ }
337
+ async function resolveStoreBySlug(container, slug, options = {}) {
338
+ return resolveSlugWithCandidates(container, slug, options.tenantId ?? null, {
339
+ queryLocale: options.locale ?? null,
340
+ headerLocale: options.headerLocale ?? null,
341
+ acceptLanguage: options.acceptLanguage ?? null
342
+ });
343
+ }
344
+ async function resolveStoreFromRequest(container, request, options = {}) {
345
+ const url = new URL(request.url);
346
+ const localeCandidates = readLocaleCandidates(request, url);
347
+ const storeSlug = url.searchParams.get("storeSlug");
348
+ if (storeSlug !== null) return resolveSlugWithCandidates(container, storeSlug, null, localeCandidates);
349
+ const host = normalizeRequestHost(readForcedHost(request) ?? request.headers.get("host"));
350
+ if (!host) throw storeNotFound("host_missing", {});
351
+ const domainMappingService = tryResolve(container, "domainMappingService");
352
+ if (!domainMappingService) throw storeNotFound("domain_routing_unavailable", { host });
353
+ const mapping = await domainMappingService.resolveByHostname(host);
354
+ if (!mapping || mapping.status !== "active") throw storeNotFound("domain_mapping_not_active", { host });
355
+ const candidates = await loadDomainCandidates(container, mapping);
356
+ const pathname = normalizeRequestPathname(options.pathname ?? url.pathname);
357
+ const candidate = selectLongestPrefixMatch(candidates, pathname);
358
+ if (!candidate?.binding) {
359
+ throw storeNotFound("domain_binding_not_found", {
360
+ host,
361
+ domainMappingId: mapping.domainMappingId,
362
+ bindingCount: candidates.length
363
+ });
364
+ }
365
+ return finalizeCandidate(
366
+ container,
367
+ candidate,
368
+ "host",
369
+ {
370
+ domainMappingId: mapping.domainMappingId,
371
+ hostname: mapping.hostname,
372
+ bindingId: candidate.binding.id,
373
+ pathPrefix: candidate.binding.pathPrefix,
374
+ isPrimary: candidate.binding.isPrimary
375
+ },
376
+ localeCandidates
377
+ );
378
+ }
379
+ export {
380
+ DEV_STORE_SLUG_ENV,
381
+ STORE_MISCONFIGURED_THROTTLE_MS,
382
+ STORE_RESOLUTION_TTL_MS,
383
+ StorefrontResolutionError,
384
+ isDevStoreSlugEnabled,
385
+ isStorefrontResolutionError,
386
+ normalizeRequestHost,
387
+ normalizeRequestPathname,
388
+ pathPrefixMatches,
389
+ resolveStoreBySlug,
390
+ resolveStoreFromRequest,
391
+ selectLongestPrefixMatch
392
+ };
393
+ //# sourceMappingURL=storeContext.js.map
@@ -0,0 +1,7 @@
1
+ {
2
+ "version": 3,
3
+ "sources": ["../../../../src/modules/ecommerce/lib/storeContext.ts"],
4
+ "sourcesContent": ["import type { EntityManager } from '@mikro-orm/postgresql'\nimport type { AssortmentScope } from '@open-mercato/shared/lib/catalog-visibility'\nimport { parseBooleanWithDefault } from '@open-mercato/shared/lib/boolean'\nimport { createLogger } from '@open-mercato/shared/lib/logger'\nimport { getTelemetryRuntime } from '@open-mercato/shared/lib/telemetry/runtime'\nimport { secretEqual } from '@open-mercato/core/modules/customer_accounts/lib/secretCompare'\nimport {\n ECOMMERCE_STORE_SLUG_PATTERN,\n ecommercePriceSortFallbackSchema,\n ecommerceStoreSettingsSchema,\n ecommerceStoreStatusSchema,\n type EcommercePriceSortFallback,\n type EcommerceStoreSettings,\n type EcommerceStoreStatus,\n} from '../data/validators'\nimport { emitEcommerceEvent } from '../events'\nimport {\n ecommerceDomainMappingTag,\n ecommerceDomainTag,\n ecommerceResolutionCache,\n ecommerceStoreTag,\n type CacheContainer,\n} from './cacheKeys'\nimport { resolveStoreLocale, type StoreLocaleCandidates } from './storeLocale'\nimport type { StoreContextChannel, StoreContextStore } from './types'\n\n/**\n * Store layer of storefront resolution (SPEC-029 \u00A74.1 steps 1\u20135 and 7, \u00A76.2, \u00A78, \u00A78.1).\n *\n * Host flow: normalized Host \u2192 `customer_accounts` `domainMappingService.resolveByHostname`\n * (returns `active` mappings only, D1) \u2192 ONE ecommerce query returning every binding of that\n * mapping joined with its store and the store's default channel binding (D6) \u2192 longest\n * `path_prefix` match on a path-segment boundary (R6). The candidate list is cached per mapping.\n *\n * Dev slug flow (D18): `?storeSlug=` only when `OM_ECOMMERCE_DEV_STORE_SLUG` is true, otherwise\n * `400`. A slug is unique within a tenant (\u00A75.1), so slug resolution is tenant-scoped when the\n * caller supplies a tenant and otherwise requires the slug to be unique across tenants \u2014 an\n * ambiguous slug answers `404`.\n *\n * The buyer layer (Step 2.7) composes `ResolvedStore` into a full `StoreContext`.\n */\n\nexport const DEV_STORE_SLUG_ENV = 'OM_ECOMMERCE_DEV_STORE_SLUG'\nexport const STORE_RESOLUTION_TTL_MS = 300_000\nexport const STORE_MISCONFIGURED_THROTTLE_MS = 3_600_000\n\nexport type StorefrontResolutionErrorCode =\n | 'store_slug_not_allowed'\n | 'store_not_found'\n | 'store_draft'\n | 'store_archived'\n | 'store_misconfigured'\n | 'portal_session_scope_mismatch'\n | 'portal_session_invalid'\n\nexport type StorefrontResolutionStatus = 400 | 401 | 403 | 404 | 410 | 503\n\nconst STOREFRONT_RESOLUTION_ERROR_MARKER = Symbol.for('@open-mercato/ecommerce/StorefrontResolutionError')\n\nexport class StorefrontResolutionError extends Error {\n readonly [STOREFRONT_RESOLUTION_ERROR_MARKER] = true\n readonly status: StorefrontResolutionStatus\n readonly code: StorefrontResolutionErrorCode\n\n constructor(status: StorefrontResolutionStatus, code: StorefrontResolutionErrorCode) {\n super(`[internal] storefront store resolution failed: ${code}`)\n this.name = 'StorefrontResolutionError'\n this.status = status\n this.code = code\n }\n}\n\n/**\n * Marker-based so it holds across bundle layers: `storeContextService` is resolved from the DI\n * container, whose registrars may have been loaded from another bundle layer than the route, so\n * the error class it throws is not always the route's `StorefrontResolutionError`.\n */\nexport function isStorefrontResolutionError(error: unknown): error is StorefrontResolutionError {\n return (\n !!error &&\n typeof error === 'object' &&\n (error as Record<symbol, unknown>)[STOREFRONT_RESOLUTION_ERROR_MARKER] === true\n )\n}\n\nexport type ResolvedStoreChannel = StoreContextChannel & {\n assortmentScope: AssortmentScope | null\n requireAuthentication: boolean\n}\n\nexport type ResolvedStoreDomain = {\n domainMappingId: string\n hostname: string\n bindingId: string\n pathPrefix: string | null\n isPrimary: boolean\n}\n\nexport type ResolvedStore = {\n source: 'host' | 'slug'\n store: StoreContextStore\n tenantId: string\n organizationId: string\n channel: ResolvedStoreChannel\n domain: ResolvedStoreDomain | null\n effectiveLocale: string\n requestedLocale: string | null\n currencyCode: string\n}\n\nexport type StoreContextContainer = CacheContainer\n\nexport type ResolveStoreFromRequestOptions = {\n pathname?: string | null\n}\n\nexport type ResolveStoreBySlugOptions = {\n tenantId?: string | null\n locale?: string | null\n headerLocale?: string | null\n acceptLanguage?: string | null\n}\n\ntype DomainMappingResolution = {\n domainMappingId: string\n hostname: string\n tenantId: string\n organizationId: string\n status: string\n}\n\ntype DomainMappingResolver = {\n resolveByHostname(hostname: string): Promise<DomainMappingResolution | null>\n}\n\ntype CandidateStore = Omit<StoreContextStore, 'status'> & {\n status: EcommerceStoreStatus\n tenantId: string\n organizationId: string\n}\n\ntype CandidateBinding = { id: string; pathPrefix: string | null; isPrimary: boolean }\n\ntype StoreCandidate = {\n store: CandidateStore\n channel: ResolvedStoreChannel | null\n binding: CandidateBinding | null\n}\n\ntype CachedStoreCandidates = { candidates: StoreCandidate[] }\n\ntype StoreResolutionDatabase = {\n ecommerce_stores: {\n id: string\n tenant_id: string\n organization_id: string\n code: string\n name: string\n slug: string\n status: string\n default_locale: string\n supported_locales: unknown\n default_currency_code: string\n settings: unknown\n deleted_at: Date | null\n }\n ecommerce_store_domain_bindings: {\n id: string\n tenant_id: string\n organization_id: string\n store_id: string\n domain_mapping_id: string\n path_prefix: string | null\n is_primary: boolean\n deleted_at: Date | null\n }\n ecommerce_store_channel_bindings: {\n id: string\n tenant_id: string\n organization_id: string\n store_id: string\n sales_channel_id: string\n price_kind_id: string | null\n assortment_scope: unknown\n price_sort_fallback: string\n is_default: boolean\n require_authentication: boolean\n deleted_at: Date | null\n }\n}\n\ntype StoreResolutionRow = {\n store_id: string\n store_tenant_id: string\n store_organization_id: string\n store_code: string\n store_name: string\n store_slug: string\n store_status: string\n store_default_locale: string\n store_supported_locales: unknown\n store_default_currency_code: string\n store_settings: unknown\n channel_binding_id: string | null\n channel_sales_channel_id: string | null\n channel_price_kind_id: string | null\n channel_assortment_scope: unknown\n channel_price_sort_fallback: string | null\n channel_require_authentication?: boolean | null\n binding_id?: string | null\n binding_path_prefix?: string | null\n binding_is_primary?: boolean | null\n}\n\nconst STORE_COLUMNS = [\n 's.id as store_id',\n 's.tenant_id as store_tenant_id',\n 's.organization_id as store_organization_id',\n 's.code as store_code',\n 's.name as store_name',\n 's.slug as store_slug',\n 's.status as store_status',\n 's.default_locale as store_default_locale',\n 's.supported_locales as store_supported_locales',\n 's.default_currency_code as store_default_currency_code',\n 's.settings as store_settings',\n 'c.id as channel_binding_id',\n 'c.sales_channel_id as channel_sales_channel_id',\n 'c.price_kind_id as channel_price_kind_id',\n 'c.assortment_scope as channel_assortment_scope',\n 'c.price_sort_fallback as channel_price_sort_fallback',\n 'c.require_authentication as channel_require_authentication',\n] as const\n\nconst FORCE_HOST_HEADER = 'x-force-host'\nconst FORCE_HOST_SECRET_HEADER = 'x-force-host-secret'\nconst SLUG_MAX_LENGTH = 120\n\nconst logger = createLogger('ecommerce').child({ component: 'store-context' })\n\ntype NotFoundReason =\n | 'host_missing'\n | 'domain_routing_unavailable'\n | 'domain_mapping_not_active'\n | 'domain_binding_not_found'\n | 'store_draft_hidden'\n | 'slug_invalid'\n | 'slug_not_found'\n | 'slug_ambiguous'\n\nconst WARN_NOT_FOUND_REASONS: ReadonlySet<NotFoundReason> = new Set([\n 'domain_routing_unavailable',\n 'domain_binding_not_found',\n 'slug_ambiguous',\n])\n\nfunction storeNotFound(reason: NotFoundReason, details: Record<string, unknown>): StorefrontResolutionError {\n const meta = { reason, ...details }\n if (WARN_NOT_FOUND_REASONS.has(reason)) {\n logger.warn('Storefront store not resolved', meta)\n } else {\n logger.debug('Storefront store not resolved', meta)\n }\n return new StorefrontResolutionError(404, 'store_not_found')\n}\n\nfunction tryResolve<T>(container: StoreContextContainer, name: string): T | null {\n try {\n const resolved = container.resolve(name) as T | null | undefined\n return resolved ?? null\n } catch {\n return null\n }\n}\n\nfunction resolveEntityManager(container: StoreContextContainer): EntityManager {\n const em = tryResolve<EntityManager>(container, 'em')\n if (!em) throw new Error('[internal] ecommerce store resolution requires the DI entity manager')\n return em\n}\n\nexport function isDevStoreSlugEnabled(): boolean {\n return parseBooleanWithDefault(process.env[DEV_STORE_SLUG_ENV], false)\n}\n\nexport function normalizeRequestHost(raw: string | null | undefined): string | null {\n if (typeof raw !== 'string') return null\n const first = raw.split(',')[0]?.trim().toLowerCase() ?? ''\n if (!first.length) return null\n const withoutPort = first.startsWith('[')\n ? first.replace(/^(\\[[^\\]]*\\])(?::\\d+)?$/, '$1')\n : first.replace(/:\\d+$/, '')\n const withoutTrailingDot = withoutPort.replace(/\\.+$/, '')\n return withoutTrailingDot.length ? withoutTrailingDot : null\n}\n\nexport function normalizeRequestPathname(pathname: string | null | undefined): string {\n if (typeof pathname !== 'string' || !pathname.length) return '/'\n const lowered = pathname.toLowerCase()\n const withLeadingSlash = lowered.startsWith('/') ? lowered : `/${lowered}`\n const withoutTrailingSlash = withLeadingSlash.replace(/\\/+$/, '')\n return withoutTrailingSlash.length ? withoutTrailingSlash : '/'\n}\n\nexport function pathPrefixMatches(pathPrefix: string | null, pathname: string): boolean {\n if (!pathPrefix) return true\n const normalizedPrefix = normalizeRequestPathname(pathPrefix)\n if (normalizedPrefix === '/') return true\n return pathname === normalizedPrefix || pathname.startsWith(`${normalizedPrefix}/`)\n}\n\nexport function selectLongestPrefixMatch<T extends { binding: { pathPrefix: string | null } | null }>(\n candidates: T[],\n pathname: string,\n): T | null {\n let best: T | null = null\n let bestLength = -1\n for (const candidate of candidates) {\n const pathPrefix = candidate.binding?.pathPrefix ?? null\n if (!pathPrefixMatches(pathPrefix, pathname)) continue\n const length = pathPrefix ? normalizeRequestPathname(pathPrefix).length : 0\n if (length > bestLength) {\n best = candidate\n bestLength = length\n }\n }\n return best\n}\n\nfunction readForcedHost(request: Request): string | null {\n if (process.env.NODE_ENV !== 'test') return null\n const expected = process.env.FORCE_HOST_SECRET\n if (!expected) return null\n if (!secretEqual(request.headers.get(FORCE_HOST_SECRET_HEADER), expected)) return null\n const forced = request.headers.get(FORCE_HOST_HEADER)\n return forced && forced.length > 0 ? forced : null\n}\n\nfunction readLocaleCandidates(request: Request, url: URL): StoreLocaleCandidates {\n return {\n queryLocale: url.searchParams.get('locale'),\n headerLocale: request.headers.get('x-locale'),\n acceptLanguage: request.headers.get('accept-language'),\n }\n}\n\nfunction parseJsonColumn(value: unknown): unknown {\n if (typeof value !== 'string') return value\n try {\n return JSON.parse(value)\n } catch {\n return null\n }\n}\n\nfunction isRecord(value: unknown): value is Record<string, unknown> {\n return typeof value === 'object' && value !== null && !Array.isArray(value)\n}\n\nfunction parseSettings(raw: unknown, storeId: string): EcommerceStoreSettings {\n const parsed = ecommerceStoreSettingsSchema.safeParse(parseJsonColumn(raw) ?? {})\n if (parsed.success) return parsed.data\n logger.warn('Store settings failed validation; serving defaults', { storeId })\n return ecommerceStoreSettingsSchema.parse({})\n}\n\nfunction parseSupportedLocales(raw: unknown, defaultLocale: string): string[] {\n const value = parseJsonColumn(raw)\n const locales = Array.isArray(value)\n ? value.filter((locale): locale is string => typeof locale === 'string' && locale.length > 0)\n : []\n return locales.includes(defaultLocale) ? locales : [defaultLocale, ...locales]\n}\n\nfunction parseStatus(raw: string): EcommerceStoreStatus {\n const parsed = ecommerceStoreStatusSchema.safeParse(raw)\n return parsed.success ? parsed.data : 'draft'\n}\n\nfunction parsePriceSortFallback(raw: string | null): EcommercePriceSortFallback {\n const parsed = ecommercePriceSortFallbackSchema.safeParse(raw)\n return parsed.success ? parsed.data : 'approximate'\n}\n\nfunction parseAssortmentScope(raw: unknown): AssortmentScope | null {\n const value = parseJsonColumn(raw)\n return isRecord(value) ? (value as AssortmentScope) : null\n}\n\nfunction toCandidate(row: StoreResolutionRow): StoreCandidate {\n const store: CandidateStore = {\n id: row.store_id,\n tenantId: row.store_tenant_id,\n organizationId: row.store_organization_id,\n code: row.store_code,\n name: row.store_name,\n slug: row.store_slug,\n status: parseStatus(row.store_status),\n defaultLocale: row.store_default_locale,\n supportedLocales: parseSupportedLocales(row.store_supported_locales, row.store_default_locale),\n defaultCurrencyCode: row.store_default_currency_code,\n settings: parseSettings(row.store_settings, row.store_id),\n }\n const channel: ResolvedStoreChannel | null =\n row.channel_binding_id && row.channel_sales_channel_id\n ? {\n channelBindingId: row.channel_binding_id,\n salesChannelId: row.channel_sales_channel_id,\n priceKindId: row.channel_price_kind_id ?? null,\n priceSortFallback: parsePriceSortFallback(row.channel_price_sort_fallback),\n assortmentScope: parseAssortmentScope(row.channel_assortment_scope),\n requireAuthentication: row.channel_require_authentication === true,\n }\n : null\n const binding: CandidateBinding | null = row.binding_id\n ? { id: row.binding_id, pathPrefix: row.binding_path_prefix ?? null, isPrimary: row.binding_is_primary === true }\n : null\n return { store, channel, binding }\n}\n\nasync function queryDomainCandidates(\n em: EntityManager,\n mapping: DomainMappingResolution,\n): Promise<StoreResolutionRow[]> {\n const db = em.getKysely<StoreResolutionDatabase>()\n return db\n .selectFrom('ecommerce_store_domain_bindings as b')\n .innerJoin('ecommerce_stores as s', (join) =>\n join\n .onRef('s.id', '=', 'b.store_id')\n .onRef('s.tenant_id', '=', 'b.tenant_id')\n .onRef('s.organization_id', '=', 'b.organization_id')\n .on('s.deleted_at', 'is', null),\n )\n .leftJoin('ecommerce_store_channel_bindings as c', (join) =>\n join\n .onRef('c.store_id', '=', 's.id')\n .onRef('c.tenant_id', '=', 's.tenant_id')\n .onRef('c.organization_id', '=', 's.organization_id')\n .on('c.is_default', '=', true)\n .on('c.deleted_at', 'is', null),\n )\n .select([\n ...STORE_COLUMNS,\n 'b.id as binding_id',\n 'b.path_prefix as binding_path_prefix',\n 'b.is_primary as binding_is_primary',\n ])\n .where('b.domain_mapping_id', '=', mapping.domainMappingId)\n .where('b.tenant_id', '=', mapping.tenantId)\n .where('b.organization_id', '=', mapping.organizationId)\n .where('b.deleted_at', 'is', null)\n .execute()\n}\n\nasync function querySlugCandidates(\n em: EntityManager,\n slug: string,\n tenantId: string | null,\n): Promise<StoreResolutionRow[]> {\n const db = em.getKysely<StoreResolutionDatabase>()\n let query = db\n .selectFrom('ecommerce_stores as s')\n .leftJoin('ecommerce_store_channel_bindings as c', (join) =>\n join\n .onRef('c.store_id', '=', 's.id')\n .onRef('c.tenant_id', '=', 's.tenant_id')\n .onRef('c.organization_id', '=', 's.organization_id')\n .on('c.is_default', '=', true)\n .on('c.deleted_at', 'is', null),\n )\n .select([...STORE_COLUMNS])\n .where('s.slug', '=', slug)\n .where('s.deleted_at', 'is', null)\n if (tenantId) query = query.where('s.tenant_id', '=', tenantId)\n return query.limit(2).execute()\n}\n\nasync function loadDomainCandidates(\n container: StoreContextContainer,\n mapping: DomainMappingResolution,\n): Promise<StoreCandidate[]> {\n const cache = ecommerceResolutionCache(container)\n const key = ['domain-mapping', mapping.domainMappingId]\n const cached = await cache.get<CachedStoreCandidates>(key)\n if (cached && Array.isArray(cached.candidates)) return cached.candidates\n const rows = await queryDomainCandidates(resolveEntityManager(container), mapping)\n const candidates = rows.map(toCandidate)\n const tags = [\n ecommerceDomainMappingTag(mapping.domainMappingId),\n ecommerceDomainTag(mapping.hostname),\n ...candidates.map((candidate) => ecommerceStoreTag(candidate.store.id)),\n ]\n await cache.set<CachedStoreCandidates>(\n key,\n { candidates },\n { ttlMs: STORE_RESOLUTION_TTL_MS, tags: Array.from(new Set(tags)) },\n )\n return candidates\n}\n\nasync function loadSlugCandidates(\n container: StoreContextContainer,\n slug: string,\n tenantId: string | null,\n): Promise<StoreCandidate[]> {\n const cache = ecommerceResolutionCache(container)\n const key = ['slug', tenantId ?? '*', slug]\n const cached = await cache.get<CachedStoreCandidates>(key)\n if (cached && Array.isArray(cached.candidates)) return cached.candidates\n const rows = await querySlugCandidates(resolveEntityManager(container), slug, tenantId)\n const candidates = rows.map(toCandidate)\n if (candidates.length > 0) {\n await cache.set<CachedStoreCandidates>(\n key,\n { candidates },\n {\n ttlMs: STORE_RESOLUTION_TTL_MS,\n tags: candidates.map((candidate) => ecommerceStoreTag(candidate.store.id)),\n },\n )\n }\n return candidates\n}\n\nasync function reportMisconfiguredStore(container: StoreContextContainer, store: CandidateStore): Promise<void> {\n const cache = ecommerceResolutionCache(container)\n const key = ['misconfigured', 'channel-binding-missing', store.id]\n const alreadyReported = await cache.get<{ reportedAt: string }>(key)\n if (alreadyReported) return\n await cache.set(key, { reportedAt: new Date().toISOString() }, { ttlMs: STORE_MISCONFIGURED_THROTTLE_MS })\n logger.warn('Store has no default channel binding; answering 503', {\n storeId: store.id,\n tenantId: store.tenantId,\n organizationId: store.organizationId,\n })\n try {\n await emitEcommerceEvent(\n 'ecommerce.store.misconfigured',\n {\n id: store.id,\n storeId: store.id,\n tenantId: store.tenantId,\n organizationId: store.organizationId,\n reason: 'channel_binding_missing',\n },\n { persistent: true, tenantId: store.tenantId, organizationId: store.organizationId },\n )\n } catch (error) {\n logger.warn('Failed to emit store misconfiguration event', { storeId: store.id, err: error })\n getTelemetryRuntime()?.reportError(error, {\n module: 'ecommerce',\n code: 'ecommerce.store_misconfigured_emit_failed',\n attributes: { storeId: store.id },\n })\n }\n}\n\nasync function finalizeCandidate(\n container: StoreContextContainer,\n candidate: StoreCandidate,\n source: ResolvedStore['source'],\n domain: ResolvedStoreDomain | null,\n localeCandidates: StoreLocaleCandidates,\n): Promise<ResolvedStore> {\n const { store } = candidate\n if (store.status === 'draft') {\n if (isDevStoreSlugEnabled()) throw new StorefrontResolutionError(403, 'store_draft')\n throw storeNotFound('store_draft_hidden', { source, storeId: store.id })\n }\n if (store.status === 'archived') throw new StorefrontResolutionError(410, 'store_archived')\n if (!candidate.channel) {\n await reportMisconfiguredStore(container, store)\n throw new StorefrontResolutionError(503, 'store_misconfigured')\n }\n const locale = resolveStoreLocale(store, localeCandidates)\n return {\n source,\n store: {\n id: store.id,\n code: store.code,\n name: store.name,\n slug: store.slug,\n status: 'active',\n defaultLocale: store.defaultLocale,\n supportedLocales: store.supportedLocales,\n defaultCurrencyCode: store.defaultCurrencyCode,\n settings: store.settings,\n },\n tenantId: store.tenantId,\n organizationId: store.organizationId,\n channel: candidate.channel,\n domain,\n effectiveLocale: locale.effectiveLocale,\n requestedLocale: locale.requestedLocale,\n currencyCode: store.defaultCurrencyCode,\n }\n}\n\nasync function resolveSlugWithCandidates(\n container: StoreContextContainer,\n rawSlug: string,\n tenantId: string | null,\n localeCandidates: StoreLocaleCandidates,\n): Promise<ResolvedStore> {\n if (!isDevStoreSlugEnabled()) throw new StorefrontResolutionError(400, 'store_slug_not_allowed')\n const slug = rawSlug.trim().toLowerCase()\n if (!slug.length || slug.length > SLUG_MAX_LENGTH || !ECOMMERCE_STORE_SLUG_PATTERN.test(slug)) {\n throw storeNotFound('slug_invalid', {})\n }\n const candidates = await loadSlugCandidates(container, slug, tenantId)\n if (candidates.length === 0) throw storeNotFound('slug_not_found', { slug })\n if (candidates.length > 1) throw storeNotFound('slug_ambiguous', { slug })\n return finalizeCandidate(container, candidates[0], 'slug', null, localeCandidates)\n}\n\nexport async function resolveStoreBySlug(\n container: StoreContextContainer,\n slug: string,\n options: ResolveStoreBySlugOptions = {},\n): Promise<ResolvedStore> {\n return resolveSlugWithCandidates(container, slug, options.tenantId ?? null, {\n queryLocale: options.locale ?? null,\n headerLocale: options.headerLocale ?? null,\n acceptLanguage: options.acceptLanguage ?? null,\n })\n}\n\nexport async function resolveStoreFromRequest(\n container: StoreContextContainer,\n request: Request,\n options: ResolveStoreFromRequestOptions = {},\n): Promise<ResolvedStore> {\n const url = new URL(request.url)\n const localeCandidates = readLocaleCandidates(request, url)\n const storeSlug = url.searchParams.get('storeSlug')\n if (storeSlug !== null) return resolveSlugWithCandidates(container, storeSlug, null, localeCandidates)\n\n const host = normalizeRequestHost(readForcedHost(request) ?? request.headers.get('host'))\n if (!host) throw storeNotFound('host_missing', {})\n const domainMappingService = tryResolve<DomainMappingResolver>(container, 'domainMappingService')\n if (!domainMappingService) throw storeNotFound('domain_routing_unavailable', { host })\n const mapping = await domainMappingService.resolveByHostname(host)\n if (!mapping || mapping.status !== 'active') throw storeNotFound('domain_mapping_not_active', { host })\n\n const candidates = await loadDomainCandidates(container, mapping)\n const pathname = normalizeRequestPathname(options.pathname ?? url.pathname)\n const candidate = selectLongestPrefixMatch(candidates, pathname)\n if (!candidate?.binding) {\n throw storeNotFound('domain_binding_not_found', {\n host,\n domainMappingId: mapping.domainMappingId,\n bindingCount: candidates.length,\n })\n }\n return finalizeCandidate(\n container,\n candidate,\n 'host',\n {\n domainMappingId: mapping.domainMappingId,\n hostname: mapping.hostname,\n bindingId: candidate.binding.id,\n pathPrefix: candidate.binding.pathPrefix,\n isPrimary: candidate.binding.isPrimary,\n },\n localeCandidates,\n )\n}\n"],
5
+ "mappings": "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",
6
+ "names": []
7
+ }
@@ -0,0 +1,43 @@
1
+ import { runWithCacheTenant } from "@open-mercato/cache";
2
+ import { composeStoreContext, resolveBuyerContext } from "./buyerContext.js";
3
+ import { buyerContextCache, ecommerceResolutionCache, ecommerceStoreTag } from "./cacheKeys.js";
4
+ import {
5
+ resolveStoreBySlug,
6
+ resolveStoreFromRequest
7
+ } from "./storeContext.js";
8
+ async function lookupStoreTenantId(container, storeId) {
9
+ let em = null;
10
+ try {
11
+ em = container.resolve("em") ?? null;
12
+ } catch {
13
+ em = null;
14
+ }
15
+ if (!em) return null;
16
+ const row = await em.getKysely().selectFrom("ecommerce_stores").select("tenant_id").where("id", "=", storeId).executeTakeFirst();
17
+ return row?.tenant_id ?? null;
18
+ }
19
+ function createStoreContextService(container) {
20
+ return {
21
+ async resolve(request, opts) {
22
+ const store = await resolveStoreFromRequest(container, request, opts);
23
+ const buyer = await runWithCacheTenant(store.tenantId, () => resolveBuyerContext(container, store, request));
24
+ return composeStoreContext(store, buyer);
25
+ },
26
+ async resolveBySlug(slug, opts = {}) {
27
+ const { request = null, ...slugOptions } = opts;
28
+ const store = await resolveStoreBySlug(container, slug, slugOptions);
29
+ const buyer = await runWithCacheTenant(store.tenantId, () => resolveBuyerContext(container, store, request));
30
+ return composeStoreContext(store, buyer);
31
+ },
32
+ async invalidate(storeId, opts = {}) {
33
+ const tags = [ecommerceStoreTag(storeId)];
34
+ await ecommerceResolutionCache(container).deleteByTags(tags);
35
+ const tenantId = opts.tenantId ?? await lookupStoreTenantId(container, storeId);
36
+ if (tenantId) await buyerContextCache(container, { tenantId, storeId }).deleteByTags(tags);
37
+ }
38
+ };
39
+ }
40
+ export {
41
+ createStoreContextService
42
+ };
43
+ //# sourceMappingURL=storeContextService.js.map
@@ -0,0 +1,7 @@
1
+ {
2
+ "version": 3,
3
+ "sources": ["../../../../src/modules/ecommerce/lib/storeContextService.ts"],
4
+ "sourcesContent": ["import type { EntityManager } from '@mikro-orm/postgresql'\nimport { runWithCacheTenant } from '@open-mercato/cache'\nimport { composeStoreContext, resolveBuyerContext } from './buyerContext'\nimport { buyerContextCache, ecommerceResolutionCache, ecommerceStoreTag, type CacheContainer } from './cacheKeys'\nimport {\n resolveStoreBySlug,\n resolveStoreFromRequest,\n type ResolveStoreBySlugOptions,\n type ResolveStoreFromRequestOptions,\n} from './storeContext'\nimport type { StoreContext } from './types'\n\n/**\n * `storeContextService` (SPEC-029 \u00A76): composes the store layer (`storeContext.ts`) and the\n * buyer layer (`buyerContext.ts`) into a full `StoreContext`. Registered per request container.\n */\n\nexport type ResolveStoreContextBySlugOptions = ResolveStoreBySlugOptions & {\n request?: Request | null\n}\n\nexport type InvalidateStoreContextOptions = { tenantId?: string | null }\n\nexport type StoreContextService = {\n resolve(request: Request, opts?: ResolveStoreFromRequestOptions): Promise<StoreContext>\n resolveBySlug(slug: string, opts?: ResolveStoreContextBySlugOptions): Promise<StoreContext>\n invalidate(storeId: string, opts?: InvalidateStoreContextOptions): Promise<void>\n}\n\ntype StoreTenantDatabase = {\n ecommerce_stores: { id: string; tenant_id: string }\n}\n\nasync function lookupStoreTenantId(container: CacheContainer, storeId: string): Promise<string | null> {\n let em: EntityManager | null = null\n try {\n em = (container.resolve('em') as EntityManager | null | undefined) ?? null\n } catch {\n em = null\n }\n if (!em) return null\n const row = await em\n .getKysely<StoreTenantDatabase>()\n .selectFrom('ecommerce_stores')\n .select('tenant_id')\n .where('id', '=', storeId)\n .executeTakeFirst()\n return row?.tenant_id ?? null\n}\n\nexport function createStoreContextService(container: CacheContainer): StoreContextService {\n return {\n async resolve(request, opts) {\n const store = await resolveStoreFromRequest(container, request, opts)\n const buyer = await runWithCacheTenant(store.tenantId, () => resolveBuyerContext(container, store, request))\n return composeStoreContext(store, buyer)\n },\n async resolveBySlug(slug, opts = {}) {\n const { request = null, ...slugOptions } = opts\n const store = await resolveStoreBySlug(container, slug, slugOptions)\n const buyer = await runWithCacheTenant(store.tenantId, () => resolveBuyerContext(container, store, request))\n return composeStoreContext(store, buyer)\n },\n async invalidate(storeId, opts = {}) {\n const tags = [ecommerceStoreTag(storeId)]\n await ecommerceResolutionCache(container).deleteByTags(tags)\n const tenantId = opts.tenantId ?? (await lookupStoreTenantId(container, storeId))\n if (tenantId) await buyerContextCache(container, { tenantId, storeId }).deleteByTags(tags)\n },\n }\n}\n"],
5
+ "mappings": "AACA,SAAS,0BAA0B;AACnC,SAAS,qBAAqB,2BAA2B;AACzD,SAAS,mBAAmB,0BAA0B,yBAA8C;AACpG;AAAA,EACE;AAAA,EACA;AAAA,OAGK;AAwBP,eAAe,oBAAoB,WAA2B,SAAyC;AACrG,MAAI,KAA2B;AAC/B,MAAI;AACF,SAAM,UAAU,QAAQ,IAAI,KAA0C;AAAA,EACxE,QAAQ;AACN,SAAK;AAAA,EACP;AACA,MAAI,CAAC,GAAI,QAAO;AAChB,QAAM,MAAM,MAAM,GACf,UAA+B,EAC/B,WAAW,kBAAkB,EAC7B,OAAO,WAAW,EAClB,MAAM,MAAM,KAAK,OAAO,EACxB,iBAAiB;AACpB,SAAO,KAAK,aAAa;AAC3B;AAEO,SAAS,0BAA0B,WAAgD;AACxF,SAAO;AAAA,IACL,MAAM,QAAQ,SAAS,MAAM;AAC3B,YAAM,QAAQ,MAAM,wBAAwB,WAAW,SAAS,IAAI;AACpE,YAAM,QAAQ,MAAM,mBAAmB,MAAM,UAAU,MAAM,oBAAoB,WAAW,OAAO,OAAO,CAAC;AAC3G,aAAO,oBAAoB,OAAO,KAAK;AAAA,IACzC;AAAA,IACA,MAAM,cAAc,MAAM,OAAO,CAAC,GAAG;AACnC,YAAM,EAAE,UAAU,MAAM,GAAG,YAAY,IAAI;AAC3C,YAAM,QAAQ,MAAM,mBAAmB,WAAW,MAAM,WAAW;AACnE,YAAM,QAAQ,MAAM,mBAAmB,MAAM,UAAU,MAAM,oBAAoB,WAAW,OAAO,OAAO,CAAC;AAC3G,aAAO,oBAAoB,OAAO,KAAK;AAAA,IACzC;AAAA,IACA,MAAM,WAAW,SAAS,OAAO,CAAC,GAAG;AACnC,YAAM,OAAO,CAAC,kBAAkB,OAAO,CAAC;AACxC,YAAM,yBAAyB,SAAS,EAAE,aAAa,IAAI;AAC3D,YAAM,WAAW,KAAK,YAAa,MAAM,oBAAoB,WAAW,OAAO;AAC/E,UAAI,SAAU,OAAM,kBAAkB,WAAW,EAAE,UAAU,QAAQ,CAAC,EAAE,aAAa,IAAI;AAAA,IAC3F;AAAA,EACF;AACF;",
6
+ "names": []
7
+ }
@@ -0,0 +1,60 @@
1
+ const LOCALE_TAG_PATTERN = /^[A-Za-z]{2,8}(?:-[A-Za-z0-9]{1,8})*$/;
2
+ const LOCALE_TAG_MAX_LENGTH = 35;
3
+ const ACCEPT_LANGUAGE_MAX_LENGTH = 1024;
4
+ const ACCEPT_LANGUAGE_MAX_ENTRIES = 20;
5
+ const QUALITY_PATTERN = /^q=([01](?:\.\d{0,3})?)$/i;
6
+ function sanitizeLocaleTag(value) {
7
+ if (typeof value !== "string") return null;
8
+ const trimmed = value.trim();
9
+ if (!trimmed.length || trimmed.length > LOCALE_TAG_MAX_LENGTH) return null;
10
+ const normalized = trimmed.replace(/_/g, "-");
11
+ return LOCALE_TAG_PATTERN.test(normalized) ? normalized : null;
12
+ }
13
+ function parseAcceptLanguage(header) {
14
+ if (typeof header !== "string" || !header.trim().length) return [];
15
+ const entries = header.slice(0, ACCEPT_LANGUAGE_MAX_LENGTH).split(",").slice(0, ACCEPT_LANGUAGE_MAX_ENTRIES).map((part, index) => {
16
+ const [rawTag, ...params] = part.split(";");
17
+ let quality = 1;
18
+ for (const param of params) {
19
+ const match = QUALITY_PATTERN.exec(param.trim());
20
+ if (match) quality = Number.parseFloat(match[1]);
21
+ }
22
+ return { tag: sanitizeLocaleTag(rawTag), quality, index };
23
+ }).filter((entry) => entry.tag !== null && entry.quality > 0);
24
+ entries.sort((left, right) => right.quality - left.quality || left.index - right.index);
25
+ return entries.map((entry) => entry.tag);
26
+ }
27
+ function baseLanguage(locale) {
28
+ return locale.toLowerCase().split("-")[0];
29
+ }
30
+ function matchSupportedLocale(candidate, supportedLocales) {
31
+ const lowered = candidate.toLowerCase();
32
+ const exact = supportedLocales.find((locale) => locale.toLowerCase() === lowered);
33
+ if (exact) return exact;
34
+ const base = baseLanguage(candidate);
35
+ const baseExact = supportedLocales.find((locale) => locale.toLowerCase() === base);
36
+ if (baseExact) return baseExact;
37
+ return supportedLocales.find((locale) => baseLanguage(locale) === base) ?? null;
38
+ }
39
+ function resolveStoreLocale(store, candidates) {
40
+ const queryLocale = sanitizeLocaleTag(candidates.queryLocale);
41
+ const headerLocale = sanitizeLocaleTag(candidates.headerLocale);
42
+ const ordered = [
43
+ ...queryLocale ? [queryLocale] : [],
44
+ ...headerLocale ? [headerLocale] : [],
45
+ ...parseAcceptLanguage(candidates.acceptLanguage)
46
+ ];
47
+ const requestedLocale = queryLocale ?? headerLocale ?? null;
48
+ for (const candidate of ordered) {
49
+ const matched = matchSupportedLocale(candidate, store.supportedLocales);
50
+ if (matched) return { effectiveLocale: matched, requestedLocale };
51
+ }
52
+ return { effectiveLocale: store.defaultLocale, requestedLocale };
53
+ }
54
+ export {
55
+ matchSupportedLocale,
56
+ parseAcceptLanguage,
57
+ resolveStoreLocale,
58
+ sanitizeLocaleTag
59
+ };
60
+ //# sourceMappingURL=storeLocale.js.map
@@ -0,0 +1,7 @@
1
+ {
2
+ "version": 3,
3
+ "sources": ["../../../../src/modules/ecommerce/lib/storeLocale.ts"],
4
+ "sourcesContent": ["/**\n * Storefront locale negotiation (SPEC-029 \u00A74.1 step 7, \u00A76.2): `?locale` \u2192 `X-Locale` \u2192\n * `Accept-Language` (q-values honored) \u2192 `store.defaultLocale`. A candidate is used only when it\n * matches one of `store.supportedLocales`; an unsupported request falls back without failing.\n */\n\nexport type StoreLocaleSettings = {\n defaultLocale: string\n supportedLocales: string[]\n}\n\nexport type StoreLocaleCandidates = {\n queryLocale?: string | null\n headerLocale?: string | null\n acceptLanguage?: string | null\n}\n\nexport type StoreLocaleResolution = {\n effectiveLocale: string\n requestedLocale: string | null\n}\n\nconst LOCALE_TAG_PATTERN = /^[A-Za-z]{2,8}(?:-[A-Za-z0-9]{1,8})*$/\nconst LOCALE_TAG_MAX_LENGTH = 35\nconst ACCEPT_LANGUAGE_MAX_LENGTH = 1024\nconst ACCEPT_LANGUAGE_MAX_ENTRIES = 20\nconst QUALITY_PATTERN = /^q=([01](?:\\.\\d{0,3})?)$/i\n\nexport function sanitizeLocaleTag(value: string | null | undefined): string | null {\n if (typeof value !== 'string') return null\n const trimmed = value.trim()\n if (!trimmed.length || trimmed.length > LOCALE_TAG_MAX_LENGTH) return null\n const normalized = trimmed.replace(/_/g, '-')\n return LOCALE_TAG_PATTERN.test(normalized) ? normalized : null\n}\n\nexport function parseAcceptLanguage(header: string | null | undefined): string[] {\n if (typeof header !== 'string' || !header.trim().length) return []\n const entries = header\n .slice(0, ACCEPT_LANGUAGE_MAX_LENGTH)\n .split(',')\n .slice(0, ACCEPT_LANGUAGE_MAX_ENTRIES)\n .map((part, index) => {\n const [rawTag, ...params] = part.split(';')\n let quality = 1\n for (const param of params) {\n const match = QUALITY_PATTERN.exec(param.trim())\n if (match) quality = Number.parseFloat(match[1])\n }\n return { tag: sanitizeLocaleTag(rawTag), quality, index }\n })\n .filter((entry): entry is { tag: string; quality: number; index: number } => entry.tag !== null && entry.quality > 0)\n entries.sort((left, right) => right.quality - left.quality || left.index - right.index)\n return entries.map((entry) => entry.tag)\n}\n\nfunction baseLanguage(locale: string): string {\n return locale.toLowerCase().split('-')[0]\n}\n\nexport function matchSupportedLocale(candidate: string, supportedLocales: string[]): string | null {\n const lowered = candidate.toLowerCase()\n const exact = supportedLocales.find((locale) => locale.toLowerCase() === lowered)\n if (exact) return exact\n const base = baseLanguage(candidate)\n const baseExact = supportedLocales.find((locale) => locale.toLowerCase() === base)\n if (baseExact) return baseExact\n return supportedLocales.find((locale) => baseLanguage(locale) === base) ?? null\n}\n\nexport function resolveStoreLocale(\n store: StoreLocaleSettings,\n candidates: StoreLocaleCandidates,\n): StoreLocaleResolution {\n const queryLocale = sanitizeLocaleTag(candidates.queryLocale)\n const headerLocale = sanitizeLocaleTag(candidates.headerLocale)\n const ordered = [\n ...(queryLocale ? [queryLocale] : []),\n ...(headerLocale ? [headerLocale] : []),\n ...parseAcceptLanguage(candidates.acceptLanguage),\n ]\n const requestedLocale = queryLocale ?? headerLocale ?? null\n for (const candidate of ordered) {\n const matched = matchSupportedLocale(candidate, store.supportedLocales)\n if (matched) return { effectiveLocale: matched, requestedLocale }\n }\n return { effectiveLocale: store.defaultLocale, requestedLocale }\n}\n"],
5
+ "mappings": "AAsBA,MAAM,qBAAqB;AAC3B,MAAM,wBAAwB;AAC9B,MAAM,6BAA6B;AACnC,MAAM,8BAA8B;AACpC,MAAM,kBAAkB;AAEjB,SAAS,kBAAkB,OAAiD;AACjF,MAAI,OAAO,UAAU,SAAU,QAAO;AACtC,QAAM,UAAU,MAAM,KAAK;AAC3B,MAAI,CAAC,QAAQ,UAAU,QAAQ,SAAS,sBAAuB,QAAO;AACtE,QAAM,aAAa,QAAQ,QAAQ,MAAM,GAAG;AAC5C,SAAO,mBAAmB,KAAK,UAAU,IAAI,aAAa;AAC5D;AAEO,SAAS,oBAAoB,QAA6C;AAC/E,MAAI,OAAO,WAAW,YAAY,CAAC,OAAO,KAAK,EAAE,OAAQ,QAAO,CAAC;AACjE,QAAM,UAAU,OACb,MAAM,GAAG,0BAA0B,EACnC,MAAM,GAAG,EACT,MAAM,GAAG,2BAA2B,EACpC,IAAI,CAAC,MAAM,UAAU;AACpB,UAAM,CAAC,QAAQ,GAAG,MAAM,IAAI,KAAK,MAAM,GAAG;AAC1C,QAAI,UAAU;AACd,eAAW,SAAS,QAAQ;AAC1B,YAAM,QAAQ,gBAAgB,KAAK,MAAM,KAAK,CAAC;AAC/C,UAAI,MAAO,WAAU,OAAO,WAAW,MAAM,CAAC,CAAC;AAAA,IACjD;AACA,WAAO,EAAE,KAAK,kBAAkB,MAAM,GAAG,SAAS,MAAM;AAAA,EAC1D,CAAC,EACA,OAAO,CAAC,UAAoE,MAAM,QAAQ,QAAQ,MAAM,UAAU,CAAC;AACtH,UAAQ,KAAK,CAAC,MAAM,UAAU,MAAM,UAAU,KAAK,WAAW,KAAK,QAAQ,MAAM,KAAK;AACtF,SAAO,QAAQ,IAAI,CAAC,UAAU,MAAM,GAAG;AACzC;AAEA,SAAS,aAAa,QAAwB;AAC5C,SAAO,OAAO,YAAY,EAAE,MAAM,GAAG,EAAE,CAAC;AAC1C;AAEO,SAAS,qBAAqB,WAAmB,kBAA2C;AACjG,QAAM,UAAU,UAAU,YAAY;AACtC,QAAM,QAAQ,iBAAiB,KAAK,CAAC,WAAW,OAAO,YAAY,MAAM,OAAO;AAChF,MAAI,MAAO,QAAO;AAClB,QAAM,OAAO,aAAa,SAAS;AACnC,QAAM,YAAY,iBAAiB,KAAK,CAAC,WAAW,OAAO,YAAY,MAAM,IAAI;AACjF,MAAI,UAAW,QAAO;AACtB,SAAO,iBAAiB,KAAK,CAAC,WAAW,aAAa,MAAM,MAAM,IAAI,KAAK;AAC7E;AAEO,SAAS,mBACd,OACA,YACuB;AACvB,QAAM,cAAc,kBAAkB,WAAW,WAAW;AAC5D,QAAM,eAAe,kBAAkB,WAAW,YAAY;AAC9D,QAAM,UAAU;AAAA,IACd,GAAI,cAAc,CAAC,WAAW,IAAI,CAAC;AAAA,IACnC,GAAI,eAAe,CAAC,YAAY,IAAI,CAAC;AAAA,IACrC,GAAG,oBAAoB,WAAW,cAAc;AAAA,EAClD;AACA,QAAM,kBAAkB,eAAe,gBAAgB;AACvD,aAAW,aAAa,SAAS;AAC/B,UAAM,UAAU,qBAAqB,WAAW,MAAM,gBAAgB;AACtE,QAAI,QAAS,QAAO,EAAE,iBAAiB,SAAS,gBAAgB;AAAA,EAClE;AACA,SAAO,EAAE,iBAAiB,MAAM,eAAe,gBAAgB;AACjE;",
6
+ "names": []
7
+ }
@@ -0,0 +1,41 @@
1
+ import { buildFeatureNotificationFromType } from "../../notifications/lib/notificationBuilder.js";
2
+ import { E } from "../../../generated/entities.ids.generated.js";
3
+ import { notificationTypes } from "../notifications.js";
4
+ import {
5
+ asPayloadRecord,
6
+ readEventScope,
7
+ readPayloadString,
8
+ tryResolveService
9
+ } from "./subscriberSupport.js";
10
+ const STORE_NOTIFICATION_FEATURE = "ecommerce.stores.manage";
11
+ function storeNotificationGroupKey(storeId, now) {
12
+ return `${storeId}:${now.toISOString().slice(0, 13)}`;
13
+ }
14
+ async function notifyStoreManagers(payload, ctx, type, now = /* @__PURE__ */ new Date()) {
15
+ const record = asPayloadRecord(payload);
16
+ const storeId = readPayloadString(record, "storeId") ?? readPayloadString(record, "id");
17
+ const { tenantId, organizationId } = readEventScope(record, ctx);
18
+ if (!storeId || !tenantId || !organizationId) return false;
19
+ const notificationService = tryResolveService(ctx, "notificationService");
20
+ if (!notificationService) return false;
21
+ const typeDef = notificationTypes.find((definition) => definition.type === type);
22
+ if (!typeDef) return false;
23
+ const input = buildFeatureNotificationFromType(typeDef, {
24
+ requiredFeature: STORE_NOTIFICATION_FEATURE,
25
+ sourceEntityType: E.ecommerce.ecommerce_store,
26
+ sourceEntityId: storeId,
27
+ linkHref: `/backend/config/ecommerce/${encodeURIComponent(storeId)}`,
28
+ groupKey: storeNotificationGroupKey(storeId, now)
29
+ });
30
+ await notificationService.createForFeature(
31
+ { ...input, restrictRecipientsToOrganization: true },
32
+ { tenantId, organizationId }
33
+ );
34
+ return true;
35
+ }
36
+ export {
37
+ STORE_NOTIFICATION_FEATURE,
38
+ notifyStoreManagers,
39
+ storeNotificationGroupKey
40
+ };
41
+ //# sourceMappingURL=storeNotifications.js.map