@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.
- 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,441 @@
|
|
|
1
|
+
import { sql } from "kysely";
|
|
2
|
+
import { findWithDecryption } from "@open-mercato/shared/lib/encryption/find";
|
|
3
|
+
import { defaultLocale } from "@open-mercato/shared/lib/i18n/config";
|
|
4
|
+
import { resolveSupportedLocale } from "@open-mercato/shared/lib/i18n/locale";
|
|
5
|
+
import { loadDictionary } from "@open-mercato/shared/lib/i18n/server";
|
|
6
|
+
import { createFallbackTranslator } from "@open-mercato/shared/lib/i18n/translate";
|
|
7
|
+
import { E } from "../../../generated/entities.ids.generated.js";
|
|
8
|
+
import {
|
|
9
|
+
CatalogOptionSchemaTemplate,
|
|
10
|
+
CatalogProductCategory,
|
|
11
|
+
CatalogProductVariant
|
|
12
|
+
} from "@open-mercato/core/modules/catalog/data/entities";
|
|
13
|
+
import {
|
|
14
|
+
CATALOG_PRODUCT_TYPES
|
|
15
|
+
} from "@open-mercato/core/modules/catalog/data/types";
|
|
16
|
+
import {
|
|
17
|
+
optionChoiceLabelTranslationField,
|
|
18
|
+
optionLabelTranslationField
|
|
19
|
+
} from "@open-mercato/core/modules/catalog/lib/optionSchemaTranslations";
|
|
20
|
+
import { compareCodeUnits } from "@open-mercato/core/modules/catalog/lib/productScopeKeys";
|
|
21
|
+
import { catalogCategoryTag, catalogProductsTag, storefrontCache, storefrontCacheValueHash } from "./cacheKeys.js";
|
|
22
|
+
import {
|
|
23
|
+
STOREFRONT_CATEGORY_ENTITY_TYPE,
|
|
24
|
+
STOREFRONT_OPTION_SCHEMA_ENTITY_TYPE,
|
|
25
|
+
STOREFRONT_TAG_ENTITY_TYPE,
|
|
26
|
+
isCategoryInAssortment,
|
|
27
|
+
localize,
|
|
28
|
+
nonEmptyString,
|
|
29
|
+
referenceId,
|
|
30
|
+
stringList
|
|
31
|
+
} from "./storefrontCatalogSupport.js";
|
|
32
|
+
const STOREFRONT_COUNT_FACETS_TTL_MS = 3e4;
|
|
33
|
+
const COUNT_FACETS_CACHE_SEGMENT = "products-count-facets";
|
|
34
|
+
const STOREFRONT_FACET_UNIVERSE_CAP = 1e4;
|
|
35
|
+
const VARIANT_ID_CHUNK = 1e4;
|
|
36
|
+
const CATALOG_PRODUCT_TYPE_LABEL_PREFIX = "catalog.products.types.";
|
|
37
|
+
function isRecord(value) {
|
|
38
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
39
|
+
}
|
|
40
|
+
function chunk(values, size) {
|
|
41
|
+
const chunks = [];
|
|
42
|
+
for (let index = 0; index < values.length; index += size) chunks.push(values.slice(index, index + size));
|
|
43
|
+
return chunks;
|
|
44
|
+
}
|
|
45
|
+
function increment(counts, key) {
|
|
46
|
+
counts.set(key, (counts.get(key) ?? 0) + 1);
|
|
47
|
+
}
|
|
48
|
+
function storefrontCountFacetCacheParts(selection, searchTerm) {
|
|
49
|
+
const options = {};
|
|
50
|
+
for (const code of Object.keys(selection.options).sort(compareCodeUnits)) {
|
|
51
|
+
const values = Array.from(new Set(selection.options[code])).sort(compareCodeUnits);
|
|
52
|
+
if (values.length) options[code] = values;
|
|
53
|
+
}
|
|
54
|
+
return [
|
|
55
|
+
COUNT_FACETS_CACHE_SEGMENT,
|
|
56
|
+
storefrontCacheValueHash({
|
|
57
|
+
search: searchTerm ?? null,
|
|
58
|
+
categoryId: selection.categoryId,
|
|
59
|
+
tagIds: Array.from(new Set(selection.tagIds)).sort(compareCodeUnits),
|
|
60
|
+
productType: selection.productType,
|
|
61
|
+
options
|
|
62
|
+
})
|
|
63
|
+
];
|
|
64
|
+
}
|
|
65
|
+
function storefrontCountFacetCacheTags(ctx, selection) {
|
|
66
|
+
return [catalogProductsTag(ctx.tenantId), ...selection.categoryId ? [catalogCategoryTag(selection.categoryId)] : []];
|
|
67
|
+
}
|
|
68
|
+
async function readCachedStorefrontCountFacets(container, ctx, selection, searchTerm) {
|
|
69
|
+
return storefrontCache(container, ctx).get(storefrontCountFacetCacheParts(selection, searchTerm), {
|
|
70
|
+
scope: "assortment"
|
|
71
|
+
});
|
|
72
|
+
}
|
|
73
|
+
async function writeCachedStorefrontCountFacets(container, ctx, selection, searchTerm, facets) {
|
|
74
|
+
await storefrontCache(container, ctx).set(storefrontCountFacetCacheParts(selection, searchTerm), facets, {
|
|
75
|
+
scope: "assortment",
|
|
76
|
+
ttlMs: STOREFRONT_COUNT_FACETS_TTL_MS,
|
|
77
|
+
tags: storefrontCountFacetCacheTags(ctx, selection)
|
|
78
|
+
});
|
|
79
|
+
}
|
|
80
|
+
async function queryStorefrontProductUniverse(runtime, filters, cap = STOREFRONT_FACET_UNIVERSE_CAP) {
|
|
81
|
+
const result = await runtime.queryEngine.query(E.catalog.catalog_product, {
|
|
82
|
+
tenantId: runtime.scope.tenantId,
|
|
83
|
+
organizationId: runtime.scope.organizationId,
|
|
84
|
+
withDeleted: runtime.scope.withDeleted,
|
|
85
|
+
filters,
|
|
86
|
+
fields: ["id", "product_type", "option_schema_id"],
|
|
87
|
+
page: { page: 1, pageSize: Math.max(1, cap) }
|
|
88
|
+
});
|
|
89
|
+
return result.total > result.items.length ? null : result.items;
|
|
90
|
+
}
|
|
91
|
+
async function loadAssignments(runtime, productIds) {
|
|
92
|
+
const { tenantId, organizationId } = runtime.decryptionScope;
|
|
93
|
+
const db = runtime.em.getKysely();
|
|
94
|
+
const categoryRows = db.selectFrom("catalog_product_category_assignments as ca").select([
|
|
95
|
+
"ca.product_id as product_id",
|
|
96
|
+
sql`'category'`.as("kind"),
|
|
97
|
+
"ca.category_id as ref_id",
|
|
98
|
+
sql`null`.as("slug"),
|
|
99
|
+
sql`null`.as("label")
|
|
100
|
+
]).where(sql`${sql.ref("ca.product_id")} = any(${productIds}::uuid[])`).where("ca.tenant_id", "=", tenantId).where("ca.organization_id", "=", organizationId);
|
|
101
|
+
const tagRows = db.selectFrom("catalog_product_tag_assignments as ta").innerJoin("catalog_product_tags as tg", "tg.id", "ta.tag_id").select([
|
|
102
|
+
"ta.product_id as product_id",
|
|
103
|
+
sql`'tag'`.as("kind"),
|
|
104
|
+
"ta.tag_id as ref_id",
|
|
105
|
+
sql`${sql.ref("tg.slug")}`.as("slug"),
|
|
106
|
+
sql`${sql.ref("tg.label")}`.as("label")
|
|
107
|
+
]).where(sql`${sql.ref("ta.product_id")} = any(${productIds}::uuid[])`).where("ta.tenant_id", "=", tenantId).where("ta.organization_id", "=", organizationId).where("tg.tenant_id", "=", tenantId).where("tg.organization_id", "=", organizationId);
|
|
108
|
+
return categoryRows.unionAll(tagRows).execute();
|
|
109
|
+
}
|
|
110
|
+
async function loadCategories(runtime) {
|
|
111
|
+
const { ctx } = runtime;
|
|
112
|
+
const rows = await findWithDecryption(
|
|
113
|
+
runtime.em,
|
|
114
|
+
CatalogProductCategory,
|
|
115
|
+
{ tenantId: ctx.tenantId, organizationId: ctx.organizationId, deletedAt: null },
|
|
116
|
+
{ fields: ["id", "name", "slug", "depth", "parentId", "isActive", "ancestorIds", "descendantIds"] },
|
|
117
|
+
runtime.decryptionScope
|
|
118
|
+
);
|
|
119
|
+
return new Map(
|
|
120
|
+
rows.map((row) => [
|
|
121
|
+
row.id,
|
|
122
|
+
{
|
|
123
|
+
id: row.id,
|
|
124
|
+
name: row.name,
|
|
125
|
+
slug: row.slug ?? null,
|
|
126
|
+
depth: typeof row.depth === "number" ? row.depth : 0,
|
|
127
|
+
parentId: row.parentId ?? null,
|
|
128
|
+
isActive: row.isActive !== false,
|
|
129
|
+
ancestorIds: stringList(row.ancestorIds),
|
|
130
|
+
descendantIds: stringList(row.descendantIds)
|
|
131
|
+
}
|
|
132
|
+
])
|
|
133
|
+
);
|
|
134
|
+
}
|
|
135
|
+
async function loadVariants(runtime, productIds) {
|
|
136
|
+
const { ctx } = runtime;
|
|
137
|
+
const batches = await Promise.all(
|
|
138
|
+
chunk(productIds, VARIANT_ID_CHUNK).map(
|
|
139
|
+
(ids) => findWithDecryption(
|
|
140
|
+
runtime.em,
|
|
141
|
+
CatalogProductVariant,
|
|
142
|
+
{ tenantId: ctx.tenantId, organizationId: ctx.organizationId, deletedAt: null, isActive: true, product: { $in: ids } },
|
|
143
|
+
{ fields: ["id", "product", "optionValues"] },
|
|
144
|
+
runtime.decryptionScope
|
|
145
|
+
)
|
|
146
|
+
)
|
|
147
|
+
);
|
|
148
|
+
const byProduct = /* @__PURE__ */ new Map();
|
|
149
|
+
for (const variant of batches.flat()) {
|
|
150
|
+
const productId = referenceId(variant.product);
|
|
151
|
+
if (!productId) continue;
|
|
152
|
+
const optionValues = {};
|
|
153
|
+
if (isRecord(variant.optionValues)) {
|
|
154
|
+
for (const [code, value] of Object.entries(variant.optionValues)) {
|
|
155
|
+
const text = nonEmptyString(value);
|
|
156
|
+
if (text !== null) optionValues[code] = text;
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
const bucket = byProduct.get(productId) ?? [];
|
|
160
|
+
bucket.push({ id: variant.id, optionValues });
|
|
161
|
+
byProduct.set(productId, bucket);
|
|
162
|
+
}
|
|
163
|
+
return byProduct;
|
|
164
|
+
}
|
|
165
|
+
async function loadTemplates(runtime, templateIds) {
|
|
166
|
+
const templates = /* @__PURE__ */ new Map();
|
|
167
|
+
if (!templateIds.length) return templates;
|
|
168
|
+
const { ctx } = runtime;
|
|
169
|
+
const rows = await findWithDecryption(
|
|
170
|
+
runtime.em,
|
|
171
|
+
CatalogOptionSchemaTemplate,
|
|
172
|
+
{ id: { $in: templateIds }, tenantId: ctx.tenantId, organizationId: ctx.organizationId, deletedAt: null, isActive: true },
|
|
173
|
+
{ fields: ["id", "schema"] },
|
|
174
|
+
runtime.decryptionScope
|
|
175
|
+
);
|
|
176
|
+
for (const row of rows) {
|
|
177
|
+
const options = isRecord(row.schema) && Array.isArray(row.schema.options) ? row.schema.options : [];
|
|
178
|
+
templates.set(
|
|
179
|
+
row.id,
|
|
180
|
+
options.filter((option) => isRecord(option) && nonEmptyString(option.code) !== null)
|
|
181
|
+
);
|
|
182
|
+
}
|
|
183
|
+
return templates;
|
|
184
|
+
}
|
|
185
|
+
async function loadStorefrontFacetSource(runtime, filters, cap = STOREFRONT_FACET_UNIVERSE_CAP) {
|
|
186
|
+
const records = await queryStorefrontProductUniverse(runtime, filters, cap);
|
|
187
|
+
if (!records) return null;
|
|
188
|
+
const products = /* @__PURE__ */ new Map();
|
|
189
|
+
for (const record of records) {
|
|
190
|
+
if (typeof record.id !== "string" || products.has(record.id)) continue;
|
|
191
|
+
products.set(record.id, {
|
|
192
|
+
id: record.id,
|
|
193
|
+
productType: nonEmptyString(record.product_type) ?? "simple",
|
|
194
|
+
templateId: nonEmptyString(record.option_schema_id),
|
|
195
|
+
categoryIds: [],
|
|
196
|
+
tagIds: []
|
|
197
|
+
});
|
|
198
|
+
}
|
|
199
|
+
const productIds = Array.from(products.keys());
|
|
200
|
+
if (!productIds.length) {
|
|
201
|
+
return { products: [], variants: /* @__PURE__ */ new Map(), categories: /* @__PURE__ */ new Map(), tags: /* @__PURE__ */ new Map(), templates: /* @__PURE__ */ new Map() };
|
|
202
|
+
}
|
|
203
|
+
const templateIds = Array.from(
|
|
204
|
+
new Set(Array.from(products.values()).flatMap((product) => product.templateId ? [product.templateId] : []))
|
|
205
|
+
);
|
|
206
|
+
const [assignments, categories, variants, templates] = await Promise.all([
|
|
207
|
+
loadAssignments(runtime, productIds),
|
|
208
|
+
loadCategories(runtime),
|
|
209
|
+
loadVariants(runtime, productIds),
|
|
210
|
+
loadTemplates(runtime, templateIds)
|
|
211
|
+
]);
|
|
212
|
+
const tags = /* @__PURE__ */ new Map();
|
|
213
|
+
for (const row of assignments) {
|
|
214
|
+
const product = products.get(String(row.product_id));
|
|
215
|
+
const refId = String(row.ref_id);
|
|
216
|
+
if (!product) continue;
|
|
217
|
+
if (row.kind === "category") {
|
|
218
|
+
if (!product.categoryIds.includes(refId)) product.categoryIds.push(refId);
|
|
219
|
+
continue;
|
|
220
|
+
}
|
|
221
|
+
if (!product.tagIds.includes(refId)) product.tagIds.push(refId);
|
|
222
|
+
if (!tags.has(refId)) tags.set(refId, { id: refId, slug: row.slug ?? "", label: row.label ?? "" });
|
|
223
|
+
}
|
|
224
|
+
return { products: Array.from(products.values()), variants, categories, tags, templates };
|
|
225
|
+
}
|
|
226
|
+
function storefrontFacetVariantIndex(source) {
|
|
227
|
+
const index = /* @__PURE__ */ new Map();
|
|
228
|
+
for (const product of source.products) {
|
|
229
|
+
index.set(product.id, (source.variants.get(product.id) ?? []).map((variant) => variant.id));
|
|
230
|
+
}
|
|
231
|
+
return index;
|
|
232
|
+
}
|
|
233
|
+
function variantMatches(variant, options, exceptCode) {
|
|
234
|
+
return Object.entries(options).every(([code, values]) => {
|
|
235
|
+
if (code === exceptCode) return true;
|
|
236
|
+
const value = variant.optionValues[code];
|
|
237
|
+
return value !== void 0 && values.includes(value);
|
|
238
|
+
});
|
|
239
|
+
}
|
|
240
|
+
function countStorefrontFacets(source, selection) {
|
|
241
|
+
const selectedCategory = selection.categoryId ? source.categories.get(selection.categoryId) : void 0;
|
|
242
|
+
const categoryReach = selection.categoryId ? /* @__PURE__ */ new Set([selection.categoryId, ...selectedCategory?.descendantIds ?? []]) : null;
|
|
243
|
+
const tagSet = selection.tagIds.length ? new Set(selection.tagIds) : null;
|
|
244
|
+
const optionCodes = Object.keys(selection.options);
|
|
245
|
+
const variantsOf = (product) => source.variants.get(product.id) ?? [];
|
|
246
|
+
const inCategory = (product) => !categoryReach || product.categoryIds.some((id) => categoryReach.has(id));
|
|
247
|
+
const inTags = (product) => !tagSet || product.tagIds.some((id) => tagSet.has(id));
|
|
248
|
+
const inType = (product) => !selection.productType || product.productType === selection.productType;
|
|
249
|
+
const inOptions = (product) => optionCodes.length === 0 || variantsOf(product).some((variant) => variantMatches(variant, selection.options, null));
|
|
250
|
+
const contributors = /* @__PURE__ */ new Map();
|
|
251
|
+
for (const category of source.categories.values()) {
|
|
252
|
+
for (const id of [category.id, ...category.descendantIds]) {
|
|
253
|
+
const bucket = contributors.get(id) ?? [];
|
|
254
|
+
bucket.push(category.id);
|
|
255
|
+
contributors.set(id, bucket);
|
|
256
|
+
}
|
|
257
|
+
}
|
|
258
|
+
const raw = {
|
|
259
|
+
categories: /* @__PURE__ */ new Map(),
|
|
260
|
+
tags: /* @__PURE__ */ new Map(),
|
|
261
|
+
productTypes: /* @__PURE__ */ new Map(),
|
|
262
|
+
options: /* @__PURE__ */ new Map()
|
|
263
|
+
};
|
|
264
|
+
const codes = new Set(optionCodes);
|
|
265
|
+
for (const product of source.products) {
|
|
266
|
+
for (const variant of variantsOf(product)) for (const code of Object.keys(variant.optionValues)) codes.add(code);
|
|
267
|
+
}
|
|
268
|
+
for (const product of source.products) {
|
|
269
|
+
const category = inCategory(product);
|
|
270
|
+
const tags = inTags(product);
|
|
271
|
+
const type = inType(product);
|
|
272
|
+
const options = inOptions(product);
|
|
273
|
+
if (tags && type && options) {
|
|
274
|
+
const reached = /* @__PURE__ */ new Set();
|
|
275
|
+
for (const assignedId of product.categoryIds) {
|
|
276
|
+
for (const categoryId of contributors.get(assignedId) ?? []) reached.add(categoryId);
|
|
277
|
+
}
|
|
278
|
+
for (const categoryId of reached) increment(raw.categories, categoryId);
|
|
279
|
+
}
|
|
280
|
+
if (category && type && options) {
|
|
281
|
+
for (const tagId of product.tagIds) increment(raw.tags, tagId);
|
|
282
|
+
}
|
|
283
|
+
if (category && tags && options) increment(raw.productTypes, product.productType);
|
|
284
|
+
if (!(category && tags && type)) continue;
|
|
285
|
+
for (const code of codes) {
|
|
286
|
+
const values = /* @__PURE__ */ new Set();
|
|
287
|
+
for (const variant of variantsOf(product)) {
|
|
288
|
+
const value = variant.optionValues[code];
|
|
289
|
+
if (value === void 0 || !variantMatches(variant, selection.options, code)) continue;
|
|
290
|
+
values.add(value);
|
|
291
|
+
}
|
|
292
|
+
if (!values.size) continue;
|
|
293
|
+
const counts = raw.options.get(code) ?? /* @__PURE__ */ new Map();
|
|
294
|
+
for (const value of values) increment(counts, value);
|
|
295
|
+
raw.options.set(code, counts);
|
|
296
|
+
}
|
|
297
|
+
}
|
|
298
|
+
return raw;
|
|
299
|
+
}
|
|
300
|
+
function sortedTemplateIds(source) {
|
|
301
|
+
return Array.from(source.templates.keys()).sort(compareCodeUnits);
|
|
302
|
+
}
|
|
303
|
+
function storefrontFacetTranslationRequests(source, raw) {
|
|
304
|
+
return [
|
|
305
|
+
{ entityType: STOREFRONT_CATEGORY_ENTITY_TYPE, ids: Array.from(raw.categories.keys()) },
|
|
306
|
+
{ entityType: STOREFRONT_TAG_ENTITY_TYPE, ids: Array.from(raw.tags.keys()) },
|
|
307
|
+
{ entityType: STOREFRONT_OPTION_SCHEMA_ENTITY_TYPE, ids: sortedTemplateIds(source) }
|
|
308
|
+
];
|
|
309
|
+
}
|
|
310
|
+
function optionDefinitions(source) {
|
|
311
|
+
const definitions = /* @__PURE__ */ new Map();
|
|
312
|
+
for (const templateId of sortedTemplateIds(source)) {
|
|
313
|
+
for (const option of source.templates.get(templateId) ?? []) {
|
|
314
|
+
if (!definitions.has(option.code)) definitions.set(option.code, { templateId, option });
|
|
315
|
+
}
|
|
316
|
+
}
|
|
317
|
+
return definitions;
|
|
318
|
+
}
|
|
319
|
+
function compareLabels(left, right) {
|
|
320
|
+
return left.label.localeCompare(right.label) || compareCodeUnits(left.code, right.code);
|
|
321
|
+
}
|
|
322
|
+
function productTypeRank(type) {
|
|
323
|
+
const index = CATALOG_PRODUCT_TYPES.indexOf(type);
|
|
324
|
+
return index === -1 ? CATALOG_PRODUCT_TYPES.length : index;
|
|
325
|
+
}
|
|
326
|
+
function labelStorefrontCountFacets(source, raw, context) {
|
|
327
|
+
const { locales, translations } = context;
|
|
328
|
+
const categoryTranslations = translations.get(STOREFRONT_CATEGORY_ENTITY_TYPE);
|
|
329
|
+
const tagTranslations = translations.get(STOREFRONT_TAG_ENTITY_TYPE);
|
|
330
|
+
const templateTranslations = translations.get(STOREFRONT_OPTION_SCHEMA_ENTITY_TYPE);
|
|
331
|
+
const categories = [];
|
|
332
|
+
for (const [id, count] of raw.categories) {
|
|
333
|
+
const category = source.categories.get(id);
|
|
334
|
+
if (!category || !category.isActive || count <= 0) continue;
|
|
335
|
+
if (!category.ancestorIds.every((ancestorId) => source.categories.get(ancestorId)?.isActive === true)) continue;
|
|
336
|
+
const lineage = { id, ancestorIds: category.ancestorIds, descendantIds: category.descendantIds };
|
|
337
|
+
if (!isCategoryInAssortment(lineage, context.assortmentScope)) continue;
|
|
338
|
+
categories.push({
|
|
339
|
+
id,
|
|
340
|
+
name: localize(category.name, categoryTranslations?.get(id), "name", locales) ?? category.name,
|
|
341
|
+
slug: category.slug,
|
|
342
|
+
depth: category.depth,
|
|
343
|
+
parentId: category.parentId,
|
|
344
|
+
count
|
|
345
|
+
});
|
|
346
|
+
}
|
|
347
|
+
categories.sort(
|
|
348
|
+
(left, right) => left.depth - right.depth || left.name.localeCompare(right.name) || compareCodeUnits(left.id, right.id)
|
|
349
|
+
);
|
|
350
|
+
const tags = [];
|
|
351
|
+
for (const [id, count] of raw.tags) {
|
|
352
|
+
const tag = source.tags.get(id);
|
|
353
|
+
if (!tag || !tag.slug || count <= 0) continue;
|
|
354
|
+
tags.push({ slug: tag.slug, label: localize(tag.label, tagTranslations?.get(id), "label", locales) ?? tag.slug, count });
|
|
355
|
+
}
|
|
356
|
+
tags.sort(
|
|
357
|
+
(left, right) => right.count - left.count || left.label.localeCompare(right.label) || compareCodeUnits(left.slug, right.slug)
|
|
358
|
+
);
|
|
359
|
+
const definitions = optionDefinitions(source);
|
|
360
|
+
const options = [];
|
|
361
|
+
for (const [code, counts] of raw.options) {
|
|
362
|
+
const definition = definitions.get(code);
|
|
363
|
+
const overlay = definition ? templateTranslations?.get(definition.templateId) : void 0;
|
|
364
|
+
const choices = Array.isArray(definition?.option.choices) ? definition.option.choices : [];
|
|
365
|
+
const values = [];
|
|
366
|
+
for (const [value, count] of counts) {
|
|
367
|
+
if (count <= 0) continue;
|
|
368
|
+
let rank = choices.findIndex((choice2) => choice2.code === value);
|
|
369
|
+
if (rank === -1) rank = choices.findIndex((choice2) => choice2.label === value);
|
|
370
|
+
const choice = rank === -1 ? null : choices[rank];
|
|
371
|
+
const label = choice ? localize(choice.label, overlay, optionChoiceLabelTranslationField(code, choice.code), locales) ?? value : value;
|
|
372
|
+
values.push({ code: value, label, count, rank: rank === -1 ? choices.length : rank });
|
|
373
|
+
}
|
|
374
|
+
if (!values.length) continue;
|
|
375
|
+
values.sort((left, right) => left.rank - right.rank || compareLabels(left, right));
|
|
376
|
+
options.push({
|
|
377
|
+
code,
|
|
378
|
+
label: definition ? localize(definition.option.label, overlay, optionLabelTranslationField(code), locales) ?? code : code,
|
|
379
|
+
values: values.map((value) => ({ code: value.code, label: value.label, count: value.count }))
|
|
380
|
+
});
|
|
381
|
+
}
|
|
382
|
+
options.sort(compareLabels);
|
|
383
|
+
const productTypes = [];
|
|
384
|
+
for (const [type, count] of raw.productTypes) {
|
|
385
|
+
if (count <= 0) continue;
|
|
386
|
+
productTypes.push({ type, label: context.productTypeLabel(type), count });
|
|
387
|
+
}
|
|
388
|
+
productTypes.sort(
|
|
389
|
+
(left, right) => productTypeRank(left.type) - productTypeRank(right.type) || compareCodeUnits(left.type, right.type)
|
|
390
|
+
);
|
|
391
|
+
return { categories, tags, options, productTypes };
|
|
392
|
+
}
|
|
393
|
+
async function loadStorefrontProductTypeLabeler(locale) {
|
|
394
|
+
const translate = createFallbackTranslator(await loadDictionary(resolveSupportedLocale(locale) ?? defaultLocale));
|
|
395
|
+
return (type) => translate(`${CATALOG_PRODUCT_TYPE_LABEL_PREFIX}${type}`, type);
|
|
396
|
+
}
|
|
397
|
+
function storefrontPriceRangeFacet(amounts, currencyCode) {
|
|
398
|
+
let min = null;
|
|
399
|
+
let max = null;
|
|
400
|
+
for (const amount of amounts) {
|
|
401
|
+
if (amount === null || !Number.isFinite(amount)) continue;
|
|
402
|
+
if (min === null || amount < min) min = amount;
|
|
403
|
+
if (max === null || amount > max) max = amount;
|
|
404
|
+
}
|
|
405
|
+
return min === null || max === null ? null : { min, max, currencyCode };
|
|
406
|
+
}
|
|
407
|
+
const AVAILABILITY_STATE_ORDER = [
|
|
408
|
+
"in_stock",
|
|
409
|
+
"low_stock",
|
|
410
|
+
"backorder",
|
|
411
|
+
"preorder",
|
|
412
|
+
"not_tracked",
|
|
413
|
+
"out_of_stock"
|
|
414
|
+
];
|
|
415
|
+
function availabilityRank(state) {
|
|
416
|
+
const index = AVAILABILITY_STATE_ORDER.indexOf(state);
|
|
417
|
+
return index === -1 ? AVAILABILITY_STATE_ORDER.length : index;
|
|
418
|
+
}
|
|
419
|
+
function storefrontAvailabilityFacet(states) {
|
|
420
|
+
const counts = /* @__PURE__ */ new Map();
|
|
421
|
+
for (const state of states) increment(counts, state);
|
|
422
|
+
return Array.from(counts.entries()).map(([state, count]) => ({ state, count })).sort((left, right) => availabilityRank(left.state) - availabilityRank(right.state));
|
|
423
|
+
}
|
|
424
|
+
export {
|
|
425
|
+
STOREFRONT_COUNT_FACETS_TTL_MS,
|
|
426
|
+
STOREFRONT_FACET_UNIVERSE_CAP,
|
|
427
|
+
countStorefrontFacets,
|
|
428
|
+
labelStorefrontCountFacets,
|
|
429
|
+
loadStorefrontFacetSource,
|
|
430
|
+
loadStorefrontProductTypeLabeler,
|
|
431
|
+
queryStorefrontProductUniverse,
|
|
432
|
+
readCachedStorefrontCountFacets,
|
|
433
|
+
storefrontAvailabilityFacet,
|
|
434
|
+
storefrontCountFacetCacheParts,
|
|
435
|
+
storefrontCountFacetCacheTags,
|
|
436
|
+
storefrontFacetTranslationRequests,
|
|
437
|
+
storefrontFacetVariantIndex,
|
|
438
|
+
storefrontPriceRangeFacet,
|
|
439
|
+
writeCachedStorefrontCountFacets
|
|
440
|
+
};
|
|
441
|
+
//# sourceMappingURL=storefrontFacets.js.map
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": 3,
|
|
3
|
+
"sources": ["../../../../src/modules/ecommerce/lib/storefrontFacets.ts"],
|
|
4
|
+
"sourcesContent": ["import type { AwilixContainer } from 'awilix'\nimport type { EntityManager } from '@mikro-orm/postgresql'\nimport { sql } from 'kysely'\nimport type { AvailabilityState } from '@open-mercato/shared/lib/availability'\nimport type { EffectiveAssortmentScope } from '@open-mercato/shared/lib/catalog-visibility'\nimport { findWithDecryption } from '@open-mercato/shared/lib/encryption/find'\nimport { defaultLocale } from '@open-mercato/shared/lib/i18n/config'\nimport { resolveSupportedLocale } from '@open-mercato/shared/lib/i18n/locale'\nimport { loadDictionary } from '@open-mercato/shared/lib/i18n/server'\nimport { createFallbackTranslator } from '@open-mercato/shared/lib/i18n/translate'\nimport type { QueryEngine, Where } from '@open-mercato/shared/lib/query/types'\nimport { E } from '#generated/entities.ids.generated'\nimport {\n CatalogOptionSchemaTemplate,\n CatalogProductCategory,\n CatalogProductVariant,\n} from '@open-mercato/core/modules/catalog/data/entities'\nimport {\n CATALOG_PRODUCT_TYPES,\n type CatalogProductOptionDefinition,\n} from '@open-mercato/core/modules/catalog/data/types'\nimport {\n optionChoiceLabelTranslationField,\n optionLabelTranslationField,\n} from '@open-mercato/core/modules/catalog/lib/optionSchemaTranslations'\nimport { compareCodeUnits } from '@open-mercato/core/modules/catalog/lib/productScopeKeys'\nimport { catalogCategoryTag, catalogProductsTag, storefrontCache, storefrontCacheValueHash } from './cacheKeys'\nimport {\n STOREFRONT_CATEGORY_ENTITY_TYPE,\n STOREFRONT_OPTION_SCHEMA_ENTITY_TYPE,\n STOREFRONT_TAG_ENTITY_TYPE,\n isCategoryInAssortment,\n localize,\n nonEmptyString,\n referenceId,\n stringList,\n type TranslationMap,\n} from './storefrontCatalogSupport'\nimport type { StorefrontProductScope } from './storefrontProductScope'\nimport type { StoreContext } from './types'\n\n/**\n * Listing facets (Storefront Public API rev 4 \u00A75.3, \u00A75.4, \u00A79.1).\n *\n * The four count facets (`categories`, `tags`, `options`, `productTypes`) are buyer-independent\n * beyond the assortment, so they are computed once per assortment scope and cached under\n * `assortmentScopeHash` (`scope: 'assortment'`). `priceRange` is buyer-priced and is computed and\n * cached with the items on the full digest \u2014 never here. `availability` counts the returned page\n * only (D21) and is computed by the listing.\n *\n * Cross-exclusion: the facet universe is the assortment scope plus the search term (the only\n * non-facet product filter). Each dimension then applies every active filter except its own, in\n * memory, over that universe \u2014 so with no filters every dimension shares one universe load, and\n * with filters no dimension costs an extra query. Option codes are separate dimensions: the\n * counts for `options[color]` keep `options[size]` applied (on the same variant) but not\n * `options[color]`. The price filter is not applied to count facets \u2014 it depends on the buyer's\n * prices, which the assortment-keyed entry must not.\n *\n * The universe is hard-capped at `STOREFRONT_FACET_UNIVERSE_CAP` products: past it the universe is\n * never loaded and the count facets degrade to empty lists (\u00A710 query budget over completeness).\n */\n\nexport const STOREFRONT_COUNT_FACETS_TTL_MS = 30_000\n\nconst COUNT_FACETS_CACHE_SEGMENT = 'products-count-facets'\n/** The most products a facet or category-count universe ever loads; larger universes are not counted. */\nexport const STOREFRONT_FACET_UNIVERSE_CAP = 10_000\nconst VARIANT_ID_CHUNK = 10_000\nconst CATALOG_PRODUCT_TYPE_LABEL_PREFIX = 'catalog.products.types.'\n\nexport type StorefrontFacets = {\n categories: Array<{ id: string; name: string; slug: string | null; depth: number; parentId: string | null; count: number }>\n tags: Array<{ slug: string; label: string; count: number }>\n priceRange: { min: number; max: number; currencyCode: string } | null\n options: Array<{ code: string; label: string; values: Array<{ code: string; label: string; count: number }> }>\n productTypes: Array<{ type: string; label: string; count: number }>\n availability: Array<{ state: AvailabilityState; count: number }>\n availabilityScope: 'page'\n total: number\n}\n\nexport type StorefrontCountFacets = Pick<StorefrontFacets, 'categories' | 'tags' | 'options' | 'productTypes'>\n\n/** The facet filters the listing actually applied (unknown or out-of-assortment selections already dropped). */\nexport type StorefrontFacetSelection = {\n categoryId: string | null\n tagIds: string[]\n productType: string | null\n options: Record<string, string[]>\n}\n\nexport type StorefrontFacetRuntime = {\n ctx: StoreContext\n em: EntityManager\n queryEngine: QueryEngine\n scope: StorefrontProductScope\n decryptionScope: { tenantId: string; organizationId: string }\n}\n\nexport type StorefrontFacetProduct = {\n id: string\n productType: string\n templateId: string | null\n categoryIds: string[]\n tagIds: string[]\n}\n\nexport type StorefrontFacetVariant = { id: string; optionValues: Record<string, string> }\n\nexport type StorefrontFacetCategory = {\n id: string\n name: string\n slug: string | null\n depth: number\n parentId: string | null\n isActive: boolean\n ancestorIds: string[]\n descendantIds: string[]\n}\n\nexport type StorefrontFacetTag = { id: string; slug: string; label: string }\n\nexport type StorefrontFacetSource = {\n products: StorefrontFacetProduct[]\n variants: Map<string, StorefrontFacetVariant[]>\n categories: Map<string, StorefrontFacetCategory>\n tags: Map<string, StorefrontFacetTag>\n templates: Map<string, CatalogProductOptionDefinition[]>\n}\n\nexport type StorefrontRawCountFacets = {\n categories: Map<string, number>\n tags: Map<string, number>\n productTypes: Map<string, number>\n options: Map<string, Map<string, number>>\n}\n\ntype UniverseRecord = { id?: unknown; product_type?: unknown; option_schema_id?: unknown }\n\ntype FacetAssignmentDatabase = {\n catalog_product_category_assignments: {\n product_id: string\n category_id: string\n tenant_id: string\n organization_id: string\n }\n catalog_product_tag_assignments: {\n product_id: string\n tag_id: string\n tenant_id: string\n organization_id: string\n }\n catalog_product_tags: {\n id: string\n slug: string\n label: string\n tenant_id: string\n organization_id: string\n }\n}\n\ntype AssignmentRow = { product_id: string; kind: string; ref_id: string; slug: string | null; label: string | null }\n\nfunction isRecord(value: unknown): value is Record<string, unknown> {\n return typeof value === 'object' && value !== null && !Array.isArray(value)\n}\n\nfunction chunk<T>(values: T[], size: number): T[][] {\n const chunks: T[][] = []\n for (let index = 0; index < values.length; index += size) chunks.push(values.slice(index, index + size))\n return chunks\n}\n\nfunction increment<K>(counts: Map<K, number>, key: K): void {\n counts.set(key, (counts.get(key) ?? 0) + 1)\n}\n\n/**\n * The count-facet key part: the resolved facet selection (unknown or out-of-assortment category\n * and tag slugs already dropped; option values are validator-bounded, sorted and de-duplicated)\n * plus the sanitized search term. Price,\n * sort, paging, availability and locale are excluded (locale is already part of the assortment\n * scope segment).\n */\nexport function storefrontCountFacetCacheParts(selection: StorefrontFacetSelection, searchTerm: string | null): string[] {\n const options: Record<string, string[]> = {}\n for (const code of Object.keys(selection.options).sort(compareCodeUnits)) {\n const values = Array.from(new Set(selection.options[code])).sort(compareCodeUnits)\n if (values.length) options[code] = values\n }\n return [\n COUNT_FACETS_CACHE_SEGMENT,\n storefrontCacheValueHash({\n search: searchTerm ?? null,\n categoryId: selection.categoryId,\n tagIds: Array.from(new Set(selection.tagIds)).sort(compareCodeUnits),\n productType: selection.productType,\n options,\n }),\n ]\n}\n\nexport function storefrontCountFacetCacheTags(ctx: StoreContext, selection: Pick<StorefrontFacetSelection, 'categoryId'>): string[] {\n return [catalogProductsTag(ctx.tenantId), ...(selection.categoryId ? [catalogCategoryTag(selection.categoryId)] : [])]\n}\n\nexport async function readCachedStorefrontCountFacets(\n container: AwilixContainer,\n ctx: StoreContext,\n selection: StorefrontFacetSelection,\n searchTerm: string | null,\n): Promise<StorefrontCountFacets | null> {\n return storefrontCache(container, ctx).get<StorefrontCountFacets>(storefrontCountFacetCacheParts(selection, searchTerm), {\n scope: 'assortment',\n })\n}\n\nexport async function writeCachedStorefrontCountFacets(\n container: AwilixContainer,\n ctx: StoreContext,\n selection: StorefrontFacetSelection,\n searchTerm: string | null,\n facets: StorefrontCountFacets,\n): Promise<void> {\n await storefrontCache(container, ctx).set(storefrontCountFacetCacheParts(selection, searchTerm), facets, {\n scope: 'assortment',\n ttlMs: STOREFRONT_COUNT_FACETS_TTL_MS,\n tags: storefrontCountFacetCacheTags(ctx, selection),\n })\n}\n\n/**\n * The scoped product ids of a facet or category-count universe, or `null` when it holds more than\n * `cap` products \u2014 one query either way, never more than `cap` rows.\n */\nexport async function queryStorefrontProductUniverse(\n runtime: StorefrontFacetRuntime,\n filters: Where,\n cap: number = STOREFRONT_FACET_UNIVERSE_CAP,\n): Promise<UniverseRecord[] | null> {\n const result = await runtime.queryEngine.query<UniverseRecord>(E.catalog.catalog_product, {\n tenantId: runtime.scope.tenantId,\n organizationId: runtime.scope.organizationId,\n withDeleted: runtime.scope.withDeleted,\n filters,\n fields: ['id', 'product_type', 'option_schema_id'],\n page: { page: 1, pageSize: Math.max(1, cap) },\n })\n return result.total > result.items.length ? null : result.items\n}\n\nasync function loadAssignments(runtime: StorefrontFacetRuntime, productIds: string[]): Promise<AssignmentRow[]> {\n const { tenantId, organizationId } = runtime.decryptionScope\n const db = runtime.em.getKysely<FacetAssignmentDatabase>()\n const categoryRows = db\n .selectFrom('catalog_product_category_assignments as ca')\n .select([\n 'ca.product_id as product_id',\n sql<string>`'category'`.as('kind'),\n 'ca.category_id as ref_id',\n sql<string | null>`null`.as('slug'),\n sql<string | null>`null`.as('label'),\n ])\n .where(sql<boolean>`${sql.ref('ca.product_id')} = any(${productIds}::uuid[])`)\n .where('ca.tenant_id', '=', tenantId)\n .where('ca.organization_id', '=', organizationId)\n const tagRows = db\n .selectFrom('catalog_product_tag_assignments as ta')\n .innerJoin('catalog_product_tags as tg', 'tg.id', 'ta.tag_id')\n .select([\n 'ta.product_id as product_id',\n sql<string>`'tag'`.as('kind'),\n 'ta.tag_id as ref_id',\n sql<string | null>`${sql.ref('tg.slug')}`.as('slug'),\n sql<string | null>`${sql.ref('tg.label')}`.as('label'),\n ])\n .where(sql<boolean>`${sql.ref('ta.product_id')} = any(${productIds}::uuid[])`)\n .where('ta.tenant_id', '=', tenantId)\n .where('ta.organization_id', '=', organizationId)\n .where('tg.tenant_id', '=', tenantId)\n .where('tg.organization_id', '=', organizationId)\n return categoryRows.unionAll(tagRows).execute()\n}\n\nasync function loadCategories(runtime: StorefrontFacetRuntime): Promise<Map<string, StorefrontFacetCategory>> {\n const { ctx } = runtime\n const rows = await findWithDecryption(\n runtime.em,\n CatalogProductCategory,\n { tenantId: ctx.tenantId, organizationId: ctx.organizationId, deletedAt: null },\n { fields: ['id', 'name', 'slug', 'depth', 'parentId', 'isActive', 'ancestorIds', 'descendantIds'] },\n runtime.decryptionScope,\n )\n return new Map(\n rows.map((row) => [\n row.id,\n {\n id: row.id,\n name: row.name,\n slug: row.slug ?? null,\n depth: typeof row.depth === 'number' ? row.depth : 0,\n parentId: row.parentId ?? null,\n isActive: row.isActive !== false,\n ancestorIds: stringList(row.ancestorIds),\n descendantIds: stringList(row.descendantIds),\n },\n ]),\n )\n}\n\nasync function loadVariants(\n runtime: StorefrontFacetRuntime,\n productIds: string[],\n): Promise<Map<string, StorefrontFacetVariant[]>> {\n const { ctx } = runtime\n const batches = await Promise.all(\n chunk(productIds, VARIANT_ID_CHUNK).map((ids) =>\n findWithDecryption(\n runtime.em,\n CatalogProductVariant,\n { tenantId: ctx.tenantId, organizationId: ctx.organizationId, deletedAt: null, isActive: true, product: { $in: ids } },\n { fields: ['id', 'product', 'optionValues'] },\n runtime.decryptionScope,\n ),\n ),\n )\n const byProduct = new Map<string, StorefrontFacetVariant[]>()\n for (const variant of batches.flat()) {\n const productId = referenceId(variant.product)\n if (!productId) continue\n const optionValues: Record<string, string> = {}\n if (isRecord(variant.optionValues)) {\n for (const [code, value] of Object.entries(variant.optionValues)) {\n const text = nonEmptyString(value)\n if (text !== null) optionValues[code] = text\n }\n }\n const bucket = byProduct.get(productId) ?? []\n bucket.push({ id: variant.id, optionValues })\n byProduct.set(productId, bucket)\n }\n return byProduct\n}\n\nasync function loadTemplates(\n runtime: StorefrontFacetRuntime,\n templateIds: string[],\n): Promise<Map<string, CatalogProductOptionDefinition[]>> {\n const templates = new Map<string, CatalogProductOptionDefinition[]>()\n if (!templateIds.length) return templates\n const { ctx } = runtime\n const rows = await findWithDecryption(\n runtime.em,\n CatalogOptionSchemaTemplate,\n { id: { $in: templateIds }, tenantId: ctx.tenantId, organizationId: ctx.organizationId, deletedAt: null, isActive: true },\n { fields: ['id', 'schema'] },\n runtime.decryptionScope,\n )\n for (const row of rows) {\n const options = isRecord(row.schema) && Array.isArray(row.schema.options) ? row.schema.options : []\n templates.set(\n row.id,\n options.filter((option): option is CatalogProductOptionDefinition => isRecord(option) && nonEmptyString(option.code) !== null),\n )\n }\n return templates\n}\n\n/**\n * The facet universe and everything the count facets read: the scoped products (one query-engine\n * call composing `filters`, normally the assortment scope plus the search clause), their category\n * and tag assignments (one union query), the tenant's categories, the products' active variants\n * and the option schema templates they use. Five queries for a universe of at most `cap` products;\n * `null` after the single universe query when it is larger. The variant list doubles as the\n * listing's variant index.\n */\nexport async function loadStorefrontFacetSource(\n runtime: StorefrontFacetRuntime,\n filters: Where,\n cap: number = STOREFRONT_FACET_UNIVERSE_CAP,\n): Promise<StorefrontFacetSource | null> {\n const records = await queryStorefrontProductUniverse(runtime, filters, cap)\n if (!records) return null\n const products = new Map<string, StorefrontFacetProduct>()\n for (const record of records) {\n if (typeof record.id !== 'string' || products.has(record.id)) continue\n products.set(record.id, {\n id: record.id,\n productType: nonEmptyString(record.product_type) ?? 'simple',\n templateId: nonEmptyString(record.option_schema_id),\n categoryIds: [],\n tagIds: [],\n })\n }\n const productIds = Array.from(products.keys())\n if (!productIds.length) {\n return { products: [], variants: new Map(), categories: new Map(), tags: new Map(), templates: new Map() }\n }\n const templateIds = Array.from(\n new Set(Array.from(products.values()).flatMap((product) => (product.templateId ? [product.templateId] : []))),\n )\n const [assignments, categories, variants, templates] = await Promise.all([\n loadAssignments(runtime, productIds),\n loadCategories(runtime),\n loadVariants(runtime, productIds),\n loadTemplates(runtime, templateIds),\n ])\n const tags = new Map<string, StorefrontFacetTag>()\n for (const row of assignments) {\n const product = products.get(String(row.product_id))\n const refId = String(row.ref_id)\n if (!product) continue\n if (row.kind === 'category') {\n if (!product.categoryIds.includes(refId)) product.categoryIds.push(refId)\n continue\n }\n if (!product.tagIds.includes(refId)) product.tagIds.push(refId)\n if (!tags.has(refId)) tags.set(refId, { id: refId, slug: row.slug ?? '', label: row.label ?? '' })\n }\n return { products: Array.from(products.values()), variants, categories, tags, templates }\n}\n\nexport function storefrontFacetVariantIndex(source: StorefrontFacetSource): Map<string, string[]> {\n const index = new Map<string, string[]>()\n for (const product of source.products) {\n index.set(product.id, (source.variants.get(product.id) ?? []).map((variant) => variant.id))\n }\n return index\n}\n\nfunction variantMatches(\n variant: StorefrontFacetVariant,\n options: Record<string, string[]>,\n exceptCode: string | null,\n): boolean {\n return Object.entries(options).every(([code, values]) => {\n if (code === exceptCode) return true\n const value = variant.optionValues[code]\n return value !== undefined && values.includes(value)\n })\n}\n\n/**\n * Cross-facet exclusion (\u00A75.4) over the facet universe: each dimension counts the products that\n * match every active filter except its own. Category counts include descendants (a product counts\n * for a category when it is assigned to it or to one of its descendants, as the filter does); option\n * counts are per option code and count a product once per value carried by any of its variants that\n * also matches the other option filters.\n */\nexport function countStorefrontFacets(\n source: StorefrontFacetSource,\n selection: StorefrontFacetSelection,\n): StorefrontRawCountFacets {\n const selectedCategory = selection.categoryId ? source.categories.get(selection.categoryId) : undefined\n const categoryReach = selection.categoryId\n ? new Set([selection.categoryId, ...(selectedCategory?.descendantIds ?? [])])\n : null\n const tagSet = selection.tagIds.length ? new Set(selection.tagIds) : null\n const optionCodes = Object.keys(selection.options)\n const variantsOf = (product: StorefrontFacetProduct) => source.variants.get(product.id) ?? []\n\n const inCategory = (product: StorefrontFacetProduct) =>\n !categoryReach || product.categoryIds.some((id) => categoryReach.has(id))\n const inTags = (product: StorefrontFacetProduct) => !tagSet || product.tagIds.some((id) => tagSet.has(id))\n const inType = (product: StorefrontFacetProduct) =>\n !selection.productType || product.productType === selection.productType\n const inOptions = (product: StorefrontFacetProduct) =>\n optionCodes.length === 0 || variantsOf(product).some((variant) => variantMatches(variant, selection.options, null))\n\n const contributors = new Map<string, string[]>()\n for (const category of source.categories.values()) {\n for (const id of [category.id, ...category.descendantIds]) {\n const bucket = contributors.get(id) ?? []\n bucket.push(category.id)\n contributors.set(id, bucket)\n }\n }\n\n const raw: StorefrontRawCountFacets = {\n categories: new Map(),\n tags: new Map(),\n productTypes: new Map(),\n options: new Map(),\n }\n const codes = new Set<string>(optionCodes)\n for (const product of source.products) {\n for (const variant of variantsOf(product)) for (const code of Object.keys(variant.optionValues)) codes.add(code)\n }\n\n for (const product of source.products) {\n const category = inCategory(product)\n const tags = inTags(product)\n const type = inType(product)\n const options = inOptions(product)\n if (tags && type && options) {\n const reached = new Set<string>()\n for (const assignedId of product.categoryIds) {\n for (const categoryId of contributors.get(assignedId) ?? []) reached.add(categoryId)\n }\n for (const categoryId of reached) increment(raw.categories, categoryId)\n }\n if (category && type && options) {\n for (const tagId of product.tagIds) increment(raw.tags, tagId)\n }\n if (category && tags && options) increment(raw.productTypes, product.productType)\n if (!(category && tags && type)) continue\n for (const code of codes) {\n const values = new Set<string>()\n for (const variant of variantsOf(product)) {\n const value = variant.optionValues[code]\n if (value === undefined || !variantMatches(variant, selection.options, code)) continue\n values.add(value)\n }\n if (!values.size) continue\n const counts = raw.options.get(code) ?? new Map<string, number>()\n for (const value of values) increment(counts, value)\n raw.options.set(code, counts)\n }\n }\n return raw\n}\n\nfunction sortedTemplateIds(source: StorefrontFacetSource): string[] {\n return Array.from(source.templates.keys()).sort(compareCodeUnits)\n}\n\n/** The overlays the count-facet labels read, for the listing's single translation query (\u00A77.2). */\nexport function storefrontFacetTranslationRequests(\n source: StorefrontFacetSource,\n raw: StorefrontRawCountFacets,\n): Array<{ entityType: string; ids: string[] }> {\n return [\n { entityType: STOREFRONT_CATEGORY_ENTITY_TYPE, ids: Array.from(raw.categories.keys()) },\n { entityType: STOREFRONT_TAG_ENTITY_TYPE, ids: Array.from(raw.tags.keys()) },\n { entityType: STOREFRONT_OPTION_SCHEMA_ENTITY_TYPE, ids: sortedTemplateIds(source) },\n ]\n}\n\nexport type StorefrontFacetLabelContext = {\n locales: string[]\n translations: Map<string, TranslationMap>\n assortmentScope: EffectiveAssortmentScope\n productTypeLabel: (type: string) => string\n}\n\ntype OptionDefinitionRef = { templateId: string; option: CatalogProductOptionDefinition }\n\nfunction optionDefinitions(source: StorefrontFacetSource): Map<string, OptionDefinitionRef> {\n const definitions = new Map<string, OptionDefinitionRef>()\n for (const templateId of sortedTemplateIds(source)) {\n for (const option of source.templates.get(templateId) ?? []) {\n if (!definitions.has(option.code)) definitions.set(option.code, { templateId, option })\n }\n }\n return definitions\n}\n\nfunction compareLabels(left: { label: string; code: string }, right: { label: string; code: string }): number {\n return left.label.localeCompare(right.label) || compareCodeUnits(left.code, right.code)\n}\n\nfunction productTypeRank(type: string): number {\n const index = (CATALOG_PRODUCT_TYPES as readonly string[]).indexOf(type)\n return index === -1 ? CATALOG_PRODUCT_TYPES.length : index\n}\n\n/**\n * Turns raw counts into the payload: categories that are active, under active ancestors and inside the buyer's assortment,\n * with localized names (depth, then name order); tags with localized labels (count, then label);\n * options with the template's translated option and choice labels (choices in template order;\n * values no template defines fall back to the stored value); product types with catalog labels.\n * Only entries with a nonzero count are listed.\n */\nexport function labelStorefrontCountFacets(\n source: StorefrontFacetSource,\n raw: StorefrontRawCountFacets,\n context: StorefrontFacetLabelContext,\n): StorefrontCountFacets {\n const { locales, translations } = context\n const categoryTranslations = translations.get(STOREFRONT_CATEGORY_ENTITY_TYPE)\n const tagTranslations = translations.get(STOREFRONT_TAG_ENTITY_TYPE)\n const templateTranslations = translations.get(STOREFRONT_OPTION_SCHEMA_ENTITY_TYPE)\n\n const categories: StorefrontCountFacets['categories'] = []\n for (const [id, count] of raw.categories) {\n const category = source.categories.get(id)\n if (!category || !category.isActive || count <= 0) continue\n if (!category.ancestorIds.every((ancestorId) => source.categories.get(ancestorId)?.isActive === true)) continue\n const lineage = { id, ancestorIds: category.ancestorIds, descendantIds: category.descendantIds }\n if (!isCategoryInAssortment(lineage, context.assortmentScope)) continue\n categories.push({\n id,\n name: localize(category.name, categoryTranslations?.get(id), 'name', locales) ?? category.name,\n slug: category.slug,\n depth: category.depth,\n parentId: category.parentId,\n count,\n })\n }\n categories.sort(\n (left, right) => left.depth - right.depth || left.name.localeCompare(right.name) || compareCodeUnits(left.id, right.id),\n )\n\n const tags: StorefrontCountFacets['tags'] = []\n for (const [id, count] of raw.tags) {\n const tag = source.tags.get(id)\n if (!tag || !tag.slug || count <= 0) continue\n tags.push({ slug: tag.slug, label: localize(tag.label, tagTranslations?.get(id), 'label', locales) ?? tag.slug, count })\n }\n tags.sort(\n (left, right) => right.count - left.count || left.label.localeCompare(right.label) || compareCodeUnits(left.slug, right.slug),\n )\n\n const definitions = optionDefinitions(source)\n const options: StorefrontCountFacets['options'] = []\n for (const [code, counts] of raw.options) {\n const definition = definitions.get(code)\n const overlay = definition ? templateTranslations?.get(definition.templateId) : undefined\n const choices = Array.isArray(definition?.option.choices) ? definition.option.choices : []\n const values: Array<{ code: string; label: string; count: number; rank: number }> = []\n for (const [value, count] of counts) {\n if (count <= 0) continue\n let rank = choices.findIndex((choice) => choice.code === value)\n if (rank === -1) rank = choices.findIndex((choice) => choice.label === value)\n const choice = rank === -1 ? null : choices[rank]\n const label = choice\n ? localize(choice.label, overlay, optionChoiceLabelTranslationField(code, choice.code), locales) ?? value\n : value\n values.push({ code: value, label, count, rank: rank === -1 ? choices.length : rank })\n }\n if (!values.length) continue\n values.sort((left, right) => left.rank - right.rank || compareLabels(left, right))\n options.push({\n code,\n label: definition\n ? localize(definition.option.label, overlay, optionLabelTranslationField(code), locales) ?? code\n : code,\n values: values.map((value) => ({ code: value.code, label: value.label, count: value.count })),\n })\n }\n options.sort(compareLabels)\n\n const productTypes: StorefrontCountFacets['productTypes'] = []\n for (const [type, count] of raw.productTypes) {\n if (count <= 0) continue\n productTypes.push({ type, label: context.productTypeLabel(type), count })\n }\n productTypes.sort(\n (left, right) => productTypeRank(left.type) - productTypeRank(right.type) || compareCodeUnits(left.type, right.type),\n )\n\n return { categories, tags, options, productTypes }\n}\n\n/** Catalog product-type labels (`catalog.products.types.<type>`) in the response locale; the type code when untranslated. */\nexport async function loadStorefrontProductTypeLabeler(locale: string): Promise<(type: string) => string> {\n const translate = createFallbackTranslator(await loadDictionary(resolveSupportedLocale(locale) ?? defaultLocale))\n return (type) => translate(`${CATALOG_PRODUCT_TYPE_LABEL_PREFIX}${type}`, type)\n}\n\n/** `priceRange` over the amounts the price filter compares against (each product's from-price). */\nexport function storefrontPriceRangeFacet(\n amounts: Iterable<number | null>,\n currencyCode: string,\n): StorefrontFacets['priceRange'] {\n let min: number | null = null\n let max: number | null = null\n for (const amount of amounts) {\n if (amount === null || !Number.isFinite(amount)) continue\n if (min === null || amount < min) min = amount\n if (max === null || amount > max) max = amount\n }\n return min === null || max === null ? null : { min, max, currencyCode }\n}\n\nconst AVAILABILITY_STATE_ORDER: readonly AvailabilityState[] = [\n 'in_stock',\n 'low_stock',\n 'backorder',\n 'preorder',\n 'not_tracked',\n 'out_of_stock',\n]\n\nfunction availabilityRank(state: AvailabilityState): number {\n const index = AVAILABILITY_STATE_ORDER.indexOf(state)\n return index === -1 ? AVAILABILITY_STATE_ORDER.length : index\n}\n\n/** Page-scoped availability counts (D21): the states of the returned page before the availability filter. */\nexport function storefrontAvailabilityFacet(states: Iterable<AvailabilityState>): StorefrontFacets['availability'] {\n const counts = new Map<AvailabilityState, number>()\n for (const state of states) increment(counts, state)\n return Array.from(counts.entries())\n .map(([state, count]) => ({ state, count }))\n .sort((left, right) => availabilityRank(left.state) - availabilityRank(right.state))\n}\n"],
|
|
5
|
+
"mappings": "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",
|
|
6
|
+
"names": ["choice"]
|
|
7
|
+
}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import { sql } from "kysely";
|
|
2
|
+
async function queryStorefrontOptionProductIds(em, scope, options, cap) {
|
|
3
|
+
const limit = Math.max(1, cap);
|
|
4
|
+
let query = em.getKysely().selectFrom("catalog_product_variants as v").select("v.product_id as product_id").distinct().where("v.tenant_id", "=", scope.tenantId).where("v.organization_id", "=", scope.organizationId).where("v.is_active", "=", true).where("v.deleted_at", "is", null);
|
|
5
|
+
for (const [code, values] of Object.entries(options)) {
|
|
6
|
+
query = query.where(sql`${sql.ref("v.option_values")} ->> ${code} = any(${values}::text[])`);
|
|
7
|
+
}
|
|
8
|
+
const rows = await query.limit(limit + 1).execute();
|
|
9
|
+
if (rows.length > limit) return null;
|
|
10
|
+
return rows.map((row) => row.product_id);
|
|
11
|
+
}
|
|
12
|
+
export {
|
|
13
|
+
queryStorefrontOptionProductIds
|
|
14
|
+
};
|
|
15
|
+
//# sourceMappingURL=storefrontOptionFilter.js.map
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": 3,
|
|
3
|
+
"sources": ["../../../../src/modules/ecommerce/lib/storefrontOptionFilter.ts"],
|
|
4
|
+
"sourcesContent": ["import { sql } from 'kysely'\nimport type { EntityManager } from '@mikro-orm/postgresql'\n\ntype OptionFilterDatabase = {\n catalog_product_variants: {\n product_id: string\n tenant_id: string\n organization_id: string\n is_active: boolean\n deleted_at: Date | null\n option_values: unknown\n }\n}\n\nexport type StorefrontOptionFilterScope = { tenantId: string; organizationId: string }\n\n/**\n * The ids of the products with an active variant matching every `options[code]` value list, or\n * `null` when more than `cap` products match. One query that reads at most `cap + 1` product ids \u2014\n * no variant is loaded or decrypted \u2014 so a broad option filter on the public listing stays bounded.\n */\nexport async function queryStorefrontOptionProductIds(\n em: EntityManager,\n scope: StorefrontOptionFilterScope,\n options: Record<string, string[]>,\n cap: number,\n): Promise<string[] | null> {\n const limit = Math.max(1, cap)\n let query = em\n .getKysely<OptionFilterDatabase>()\n .selectFrom('catalog_product_variants as v')\n .select('v.product_id as product_id')\n .distinct()\n .where('v.tenant_id', '=', scope.tenantId)\n .where('v.organization_id', '=', scope.organizationId)\n .where('v.is_active', '=', true)\n .where('v.deleted_at', 'is', null)\n for (const [code, values] of Object.entries(options)) {\n query = query.where(sql<boolean>`${sql.ref('v.option_values')} ->> ${code} = any(${values}::text[])`)\n }\n const rows = await query.limit(limit + 1).execute()\n if (rows.length > limit) return null\n return rows.map((row) => row.product_id)\n}\n"],
|
|
5
|
+
"mappings": "AAAA,SAAS,WAAW;AAqBpB,eAAsB,gCACpB,IACA,OACA,SACA,KAC0B;AAC1B,QAAM,QAAQ,KAAK,IAAI,GAAG,GAAG;AAC7B,MAAI,QAAQ,GACT,UAAgC,EAChC,WAAW,+BAA+B,EAC1C,OAAO,4BAA4B,EACnC,SAAS,EACT,MAAM,eAAe,KAAK,MAAM,QAAQ,EACxC,MAAM,qBAAqB,KAAK,MAAM,cAAc,EACpD,MAAM,eAAe,KAAK,IAAI,EAC9B,MAAM,gBAAgB,MAAM,IAAI;AACnC,aAAW,CAAC,MAAM,MAAM,KAAK,OAAO,QAAQ,OAAO,GAAG;AACpD,YAAQ,MAAM,MAAM,MAAe,IAAI,IAAI,iBAAiB,CAAC,QAAQ,IAAI,UAAU,MAAM,WAAW;AAAA,EACtG;AACA,QAAM,OAAO,MAAM,MAAM,MAAM,QAAQ,CAAC,EAAE,QAAQ;AAClD,MAAI,KAAK,SAAS,MAAO,QAAO;AAChC,SAAO,KAAK,IAAI,CAAC,QAAQ,IAAI,UAAU;AACzC;",
|
|
6
|
+
"names": []
|
|
7
|
+
}
|