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