@open-mercato/core 0.9.1-develop.7473.1.de5b22888f → 0.9.1-develop.7475.1.28ca93ed97
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.turbo/turbo-build.log +2 -2
- package/dist/generated/entities/catalog_price_history_entry/index.js +53 -0
- package/dist/generated/entities/catalog_price_history_entry/index.js.map +7 -0
- package/dist/generated/entities/customer_group_terms/index.js +2 -0
- package/dist/generated/entities/customer_group_terms/index.js.map +2 -2
- package/dist/generated/entities/ecommerce_store/index.js +33 -0
- package/dist/generated/entities/ecommerce_store/index.js.map +7 -0
- package/dist/generated/entities/ecommerce_store_channel_binding/index.js +29 -0
- package/dist/generated/entities/ecommerce_store_channel_binding/index.js.map +7 -0
- package/dist/generated/entities/ecommerce_store_domain_binding/index.js +23 -0
- package/dist/generated/entities/ecommerce_store_domain_binding/index.js.map +7 -0
- package/dist/generated/entities.ids.generated.js +8 -1
- package/dist/generated/entities.ids.generated.js.map +2 -2
- package/dist/generated/entity-fields-registry.js +72 -0
- package/dist/generated/entity-fields-registry.js.map +2 -2
- package/dist/helpers/integration/customerGroupsFixtures.js.map +2 -2
- package/dist/modules/catalog/acl.js +8 -1
- package/dist/modules/catalog/acl.js.map +2 -2
- package/dist/modules/catalog/api/config/omnibus/route.js +184 -0
- package/dist/modules/catalog/api/config/omnibus/route.js.map +7 -0
- package/dist/modules/catalog/api/prices/history/route.js +131 -0
- package/dist/modules/catalog/api/prices/history/route.js.map +7 -0
- package/dist/modules/catalog/api/prices/omnibus-preview/route.js +184 -0
- package/dist/modules/catalog/api/prices/omnibus-preview/route.js.map +7 -0
- package/dist/modules/catalog/api/products/route.js +54 -245
- package/dist/modules/catalog/api/products/route.js.map +3 -3
- package/dist/modules/catalog/api/tags/route.js +6 -0
- package/dist/modules/catalog/api/tags/route.js.map +2 -2
- package/dist/modules/catalog/backend/config/catalog/page.js +3 -1
- package/dist/modules/catalog/backend/config/catalog/page.js.map +2 -2
- package/dist/modules/catalog/cli.js +80 -2
- package/dist/modules/catalog/cli.js.map +2 -2
- package/dist/modules/catalog/commands/categories.js +51 -2
- package/dist/modules/catalog/commands/categories.js.map +2 -2
- package/dist/modules/catalog/commands/prices.js +43 -1
- package/dist/modules/catalog/commands/prices.js.map +2 -2
- package/dist/modules/catalog/commands/products.js +71 -0
- package/dist/modules/catalog/commands/products.js.map +2 -2
- package/dist/modules/catalog/commands/variants.js +7 -0
- package/dist/modules/catalog/commands/variants.js.map +2 -2
- package/dist/modules/catalog/components/OmnibusSettings.js +703 -0
- package/dist/modules/catalog/components/OmnibusSettings.js.map +7 -0
- package/dist/modules/catalog/components/PriceEditorOmnibusRow.js +152 -0
- package/dist/modules/catalog/components/PriceEditorOmnibusRow.js.map +7 -0
- package/dist/modules/catalog/components/lookupSources.js +31 -0
- package/dist/modules/catalog/components/lookupSources.js.map +7 -0
- package/dist/modules/catalog/components/prices/PriceScopeSelectors.js +8 -2
- package/dist/modules/catalog/components/prices/PriceScopeSelectors.js.map +2 -2
- package/dist/modules/catalog/components/products/VariantBuilder.js +11 -1
- package/dist/modules/catalog/components/products/VariantBuilder.js.map +2 -2
- package/dist/modules/catalog/data/entities.js +122 -1
- package/dist/modules/catalog/data/entities.js.map +2 -2
- package/dist/modules/catalog/data/validators.js +78 -0
- package/dist/modules/catalog/data/validators.js.map +2 -2
- package/dist/modules/catalog/di.js +18 -0
- package/dist/modules/catalog/di.js.map +2 -2
- package/dist/modules/catalog/lib/omnibus.js +285 -0
- package/dist/modules/catalog/lib/omnibus.js.map +7 -0
- package/dist/modules/catalog/lib/omnibusBackfill.js +146 -0
- package/dist/modules/catalog/lib/omnibusBackfill.js.map +7 -0
- package/dist/modules/catalog/lib/omnibusCache.js +89 -0
- package/dist/modules/catalog/lib/omnibusCache.js.map +7 -0
- package/dist/modules/catalog/lib/omnibusConfig.js +64 -0
- package/dist/modules/catalog/lib/omnibusConfig.js.map +7 -0
- package/dist/modules/catalog/lib/omnibusHistoryQueries.js +191 -0
- package/dist/modules/catalog/lib/omnibusHistoryQueries.js.map +7 -0
- package/dist/modules/catalog/lib/omnibusPresentedEntry.js +73 -0
- package/dist/modules/catalog/lib/omnibusPresentedEntry.js.map +7 -0
- package/dist/modules/catalog/lib/omnibusProductListEnrichment.js +94 -0
- package/dist/modules/catalog/lib/omnibusProductListEnrichment.js.map +7 -0
- package/dist/modules/catalog/lib/omnibusTypes.js +115 -0
- package/dist/modules/catalog/lib/omnibusTypes.js.map +7 -0
- package/dist/modules/catalog/lib/optionSchemaTranslations.js +55 -0
- package/dist/modules/catalog/lib/optionSchemaTranslations.js.map +7 -0
- package/dist/modules/catalog/lib/priceHistoryQuery.js +103 -0
- package/dist/modules/catalog/lib/priceHistoryQuery.js.map +7 -0
- package/dist/modules/catalog/lib/pricing.js +26 -2
- package/dist/modules/catalog/lib/pricing.js.map +2 -2
- package/dist/modules/catalog/lib/productFilters.js +273 -0
- package/dist/modules/catalog/lib/productFilters.js.map +7 -0
- package/dist/modules/catalog/lib/productScopeKeys.js +105 -0
- package/dist/modules/catalog/lib/productScopeKeys.js.map +7 -0
- package/dist/modules/catalog/lib/productScopeReindex.js +73 -0
- package/dist/modules/catalog/lib/productScopeReindex.js.map +7 -0
- package/dist/modules/catalog/migrations/Migration20261005105352_catalog.js +39 -0
- package/dist/modules/catalog/migrations/Migration20261005105352_catalog.js.map +7 -0
- package/dist/modules/catalog/migrations/Migration20261005150824_catalog.js +39 -0
- package/dist/modules/catalog/migrations/Migration20261005150824_catalog.js.map +7 -0
- package/dist/modules/catalog/services/catalogOmnibusService.js +480 -0
- package/dist/modules/catalog/services/catalogOmnibusService.js.map +7 -0
- package/dist/modules/catalog/setup.js +3 -1
- package/dist/modules/catalog/setup.js.map +2 -2
- package/dist/modules/catalog/subscribers/product-scope-keys-category-deleted.js +16 -0
- package/dist/modules/catalog/subscribers/product-scope-keys-category-deleted.js.map +7 -0
- package/dist/modules/catalog/subscribers/product-scope-keys-category-updated.js +17 -0
- package/dist/modules/catalog/subscribers/product-scope-keys-category-updated.js.map +7 -0
- package/dist/modules/catalog/translations.js +10 -0
- package/dist/modules/catalog/translations.js.map +2 -2
- package/dist/modules/configs/lib/upgrade-actions.js +28 -0
- package/dist/modules/configs/lib/upgrade-actions.js.map +2 -2
- package/dist/modules/customer_groups/api/customer-groups/[id]/terms/route.js +5 -0
- package/dist/modules/customer_groups/api/customer-groups/[id]/terms/route.js.map +2 -2
- package/dist/modules/customer_groups/components/CustomerGroupTermsSection.js +111 -3
- package/dist/modules/customer_groups/components/CustomerGroupTermsSection.js.map +2 -2
- package/dist/modules/customer_groups/components/customerGroupAssortmentScope.js +43 -0
- package/dist/modules/customer_groups/components/customerGroupAssortmentScope.js.map +7 -0
- package/dist/modules/customer_groups/data/entities.js +3 -0
- package/dist/modules/customer_groups/data/entities.js.map +2 -2
- package/dist/modules/customer_groups/data/validators.js +9 -0
- package/dist/modules/customer_groups/data/validators.js.map +2 -2
- package/dist/modules/customer_groups/migrations/Migration20261005110250_customer_groups.js +17 -0
- package/dist/modules/customer_groups/migrations/Migration20261005110250_customer_groups.js.map +7 -0
- package/dist/modules/customer_groups/services/customerGroupsService.js +62 -15
- package/dist/modules/customer_groups/services/customerGroupsService.js.map +2 -2
- package/dist/modules/ecommerce/acl.js +13 -0
- package/dist/modules/ecommerce/acl.js.map +7 -0
- package/dist/modules/ecommerce/api/domain-mappings/route.js +72 -0
- package/dist/modules/ecommerce/api/domain-mappings/route.js.map +7 -0
- package/dist/modules/ecommerce/api/openapi.js +27 -0
- package/dist/modules/ecommerce/api/openapi.js.map +7 -0
- package/dist/modules/ecommerce/api/store-channel-bindings/[id]/assortment-count/route.js +109 -0
- package/dist/modules/ecommerce/api/store-channel-bindings/[id]/assortment-count/route.js.map +7 -0
- package/dist/modules/ecommerce/api/store-channel-bindings/crud.js +265 -0
- package/dist/modules/ecommerce/api/store-channel-bindings/crud.js.map +7 -0
- package/dist/modules/ecommerce/api/store-channel-bindings/route.js +67 -0
- package/dist/modules/ecommerce/api/store-channel-bindings/route.js.map +7 -0
- package/dist/modules/ecommerce/api/store-domain-bindings/crud.js +296 -0
- package/dist/modules/ecommerce/api/store-domain-bindings/crud.js.map +7 -0
- package/dist/modules/ecommerce/api/store-domain-bindings/route.js +73 -0
- package/dist/modules/ecommerce/api/store-domain-bindings/route.js.map +7 -0
- package/dist/modules/ecommerce/api/storefront/categories/[slug]/route.js +87 -0
- package/dist/modules/ecommerce/api/storefront/categories/[slug]/route.js.map +7 -0
- package/dist/modules/ecommerce/api/storefront/categories/openapiSchemas.js +50 -0
- package/dist/modules/ecommerce/api/storefront/categories/openapiSchemas.js.map +7 -0
- package/dist/modules/ecommerce/api/storefront/categories/route.js +75 -0
- package/dist/modules/ecommerce/api/storefront/categories/route.js.map +7 -0
- package/dist/modules/ecommerce/api/storefront/context/route.js +163 -0
- package/dist/modules/ecommerce/api/storefront/context/route.js.map +7 -0
- package/dist/modules/ecommerce/api/storefront/products/[idOrHandle]/route.js +87 -0
- package/dist/modules/ecommerce/api/storefront/products/[idOrHandle]/route.js.map +7 -0
- package/dist/modules/ecommerce/api/storefront/products/openapiSchemas.js +177 -0
- package/dist/modules/ecommerce/api/storefront/products/openapiSchemas.js.map +7 -0
- package/dist/modules/ecommerce/api/storefront/products/route.js +78 -0
- package/dist/modules/ecommerce/api/storefront/products/route.js.map +7 -0
- package/dist/modules/ecommerce/api/storefront/search/suggest/openapiSchemas.js +25 -0
- package/dist/modules/ecommerce/api/storefront/search/suggest/openapiSchemas.js.map +7 -0
- package/dist/modules/ecommerce/api/storefront/search/suggest/route.js +75 -0
- package/dist/modules/ecommerce/api/storefront/search/suggest/route.js.map +7 -0
- package/dist/modules/ecommerce/api/storefront/storefrontRouteSupport.js +58 -0
- package/dist/modules/ecommerce/api/storefront/storefrontRouteSupport.js.map +7 -0
- package/dist/modules/ecommerce/api/stores/[id]/branding/route.js +183 -0
- package/dist/modules/ecommerce/api/stores/[id]/branding/route.js.map +7 -0
- package/dist/modules/ecommerce/api/stores/[id]/preview-branding/route.js +92 -0
- package/dist/modules/ecommerce/api/stores/[id]/preview-branding/route.js.map +7 -0
- package/dist/modules/ecommerce/api/stores/crud.js +401 -0
- package/dist/modules/ecommerce/api/stores/crud.js.map +7 -0
- package/dist/modules/ecommerce/api/stores/route.js +64 -0
- package/dist/modules/ecommerce/api/stores/route.js.map +7 -0
- package/dist/modules/ecommerce/backend/config/ecommerce/[id]/page.js +9 -0
- package/dist/modules/ecommerce/backend/config/ecommerce/[id]/page.js.map +7 -0
- package/dist/modules/ecommerce/backend/config/ecommerce/[id]/page.meta.js +16 -0
- package/dist/modules/ecommerce/backend/config/ecommerce/[id]/page.meta.js.map +7 -0
- package/dist/modules/ecommerce/backend/config/ecommerce/page.js +9 -0
- package/dist/modules/ecommerce/backend/config/ecommerce/page.js.map +7 -0
- package/dist/modules/ecommerce/backend/config/ecommerce/page.meta.js +16 -0
- package/dist/modules/ecommerce/backend/config/ecommerce/page.meta.js.map +7 -0
- package/dist/modules/ecommerce/commands/stores.js +110 -0
- package/dist/modules/ecommerce/commands/stores.js.map +7 -0
- package/dist/modules/ecommerce/components/ChannelAssortmentCount.js +90 -0
- package/dist/modules/ecommerce/components/ChannelAssortmentCount.js.map +7 -0
- package/dist/modules/ecommerce/components/ChannelBindingFormSections.js +33 -0
- package/dist/modules/ecommerce/components/ChannelBindingFormSections.js.map +7 -0
- package/dist/modules/ecommerce/components/ChannelLookupPicker.js +11 -0
- package/dist/modules/ecommerce/components/ChannelLookupPicker.js.map +7 -0
- package/dist/modules/ecommerce/components/DomainStatusWarning.js +24 -0
- package/dist/modules/ecommerce/components/DomainStatusWarning.js.map +7 -0
- package/dist/modules/ecommerce/components/StoreAvailabilityDefaultsSection.js +190 -0
- package/dist/modules/ecommerce/components/StoreAvailabilityDefaultsSection.js.map +7 -0
- package/dist/modules/ecommerce/components/StoreBrandingControls.js +67 -0
- package/dist/modules/ecommerce/components/StoreBrandingControls.js.map +7 -0
- package/dist/modules/ecommerce/components/StoreBrandingPreview.js +69 -0
- package/dist/modules/ecommerce/components/StoreBrandingPreview.js.map +7 -0
- package/dist/modules/ecommerce/components/StoreBrandingTab.js +218 -0
- package/dist/modules/ecommerce/components/StoreBrandingTab.js.map +7 -0
- package/dist/modules/ecommerce/components/StoreChannelBindingDialog.js +270 -0
- package/dist/modules/ecommerce/components/StoreChannelBindingDialog.js.map +7 -0
- package/dist/modules/ecommerce/components/StoreChannelsTab.js +313 -0
- package/dist/modules/ecommerce/components/StoreChannelsTab.js.map +7 -0
- package/dist/modules/ecommerce/components/StoreCreateDialog.js +164 -0
- package/dist/modules/ecommerce/components/StoreCreateDialog.js.map +7 -0
- package/dist/modules/ecommerce/components/StoreDomainBindingDialog.js +162 -0
- package/dist/modules/ecommerce/components/StoreDomainBindingDialog.js.map +7 -0
- package/dist/modules/ecommerce/components/StoreDomainsTab.js +357 -0
- package/dist/modules/ecommerce/components/StoreDomainsTab.js.map +7 -0
- package/dist/modules/ecommerce/components/StoreEditPage.js +155 -0
- package/dist/modules/ecommerce/components/StoreEditPage.js.map +7 -0
- package/dist/modules/ecommerce/components/StoreGeneralTab.js +167 -0
- package/dist/modules/ecommerce/components/StoreGeneralTab.js.map +7 -0
- package/dist/modules/ecommerce/components/StoreListPage.js +290 -0
- package/dist/modules/ecommerce/components/StoreListPage.js.map +7 -0
- package/dist/modules/ecommerce/components/StoreSeoTab.js +132 -0
- package/dist/modules/ecommerce/components/StoreSeoTab.js.map +7 -0
- package/dist/modules/ecommerce/components/StoreStatusBadge.js +20 -0
- package/dist/modules/ecommerce/components/StoreStatusBadge.js.map +7 -0
- package/dist/modules/ecommerce/components/channelLookups.js +28 -0
- package/dist/modules/ecommerce/components/channelLookups.js.map +7 -0
- package/dist/modules/ecommerce/components/storeAdmin.js +41 -0
- package/dist/modules/ecommerce/components/storeAdmin.js.map +7 -0
- package/dist/modules/ecommerce/components/storeBrandingForm.js +197 -0
- package/dist/modules/ecommerce/components/storeBrandingForm.js.map +7 -0
- package/dist/modules/ecommerce/components/storeChannels.js +140 -0
- package/dist/modules/ecommerce/components/storeChannels.js.map +7 -0
- package/dist/modules/ecommerce/components/storeDomains.js +167 -0
- package/dist/modules/ecommerce/components/storeDomains.js.map +7 -0
- package/dist/modules/ecommerce/components/storeEditTabs.js +62 -0
- package/dist/modules/ecommerce/components/storeEditTabs.js.map +7 -0
- package/dist/modules/ecommerce/components/storeGeneral.js +95 -0
- package/dist/modules/ecommerce/components/storeGeneral.js.map +7 -0
- package/dist/modules/ecommerce/components/storeSeo.js +69 -0
- package/dist/modules/ecommerce/components/storeSeo.js.map +7 -0
- package/dist/modules/ecommerce/components/useChannelAssortmentCount.js +45 -0
- package/dist/modules/ecommerce/components/useChannelAssortmentCount.js.map +7 -0
- package/dist/modules/ecommerce/components/useStoreAccess.js +28 -0
- package/dist/modules/ecommerce/components/useStoreAccess.js.map +7 -0
- package/dist/modules/ecommerce/data/enrichers.js +157 -0
- package/dist/modules/ecommerce/data/enrichers.js.map +7 -0
- package/dist/modules/ecommerce/data/entities.js +197 -0
- package/dist/modules/ecommerce/data/entities.js.map +7 -0
- package/dist/modules/ecommerce/data/validators.js +406 -0
- package/dist/modules/ecommerce/data/validators.js.map +7 -0
- package/dist/modules/ecommerce/di.js +11 -0
- package/dist/modules/ecommerce/di.js.map +7 -0
- package/dist/modules/ecommerce/events.js +27 -0
- package/dist/modules/ecommerce/events.js.map +7 -0
- package/dist/modules/ecommerce/index.js +16 -0
- package/dist/modules/ecommerce/index.js.map +7 -0
- package/dist/modules/ecommerce/lib/assortmentCount.js +112 -0
- package/dist/modules/ecommerce/lib/assortmentCount.js.map +7 -0
- package/dist/modules/ecommerce/lib/brandingStyles.js +184 -0
- package/dist/modules/ecommerce/lib/brandingStyles.js.map +7 -0
- package/dist/modules/ecommerce/lib/buyerContext.js +284 -0
- package/dist/modules/ecommerce/lib/buyerContext.js.map +7 -0
- package/dist/modules/ecommerce/lib/cacheInvalidation.js +186 -0
- package/dist/modules/ecommerce/lib/cacheInvalidation.js.map +7 -0
- package/dist/modules/ecommerce/lib/cacheKeys.js +236 -0
- package/dist/modules/ecommerce/lib/cacheKeys.js.map +7 -0
- package/dist/modules/ecommerce/lib/createConflictRecheck.js +29 -0
- package/dist/modules/ecommerce/lib/createConflictRecheck.js.map +7 -0
- package/dist/modules/ecommerce/lib/crudEvents.js +94 -0
- package/dist/modules/ecommerce/lib/crudEvents.js.map +7 -0
- package/dist/modules/ecommerce/lib/crudSupport.js +64 -0
- package/dist/modules/ecommerce/lib/crudSupport.js.map +7 -0
- package/dist/modules/ecommerce/lib/domainBindingRebind.js +99 -0
- package/dist/modules/ecommerce/lib/domainBindingRebind.js.map +7 -0
- package/dist/modules/ecommerce/lib/domainMappingSummaries.js +39 -0
- package/dist/modules/ecommerce/lib/domainMappingSummaries.js.map +7 -0
- package/dist/modules/ecommerce/lib/exclusiveFlag.js +57 -0
- package/dist/modules/ecommerce/lib/exclusiveFlag.js.map +7 -0
- package/dist/modules/ecommerce/lib/references.js +70 -0
- package/dist/modules/ecommerce/lib/references.js.map +7 -0
- package/dist/modules/ecommerce/lib/seedDraftStore.js +99 -0
- package/dist/modules/ecommerce/lib/seedDraftStore.js.map +7 -0
- package/dist/modules/ecommerce/lib/storeBindingCascade.js +109 -0
- package/dist/modules/ecommerce/lib/storeBindingCascade.js.map +7 -0
- package/dist/modules/ecommerce/lib/storeBranding.js +40 -0
- package/dist/modules/ecommerce/lib/storeBranding.js.map +7 -0
- package/dist/modules/ecommerce/lib/storeBrandingRoute.js +76 -0
- package/dist/modules/ecommerce/lib/storeBrandingRoute.js.map +7 -0
- package/dist/modules/ecommerce/lib/storeContext.js +393 -0
- package/dist/modules/ecommerce/lib/storeContext.js.map +7 -0
- package/dist/modules/ecommerce/lib/storeContextService.js +43 -0
- package/dist/modules/ecommerce/lib/storeContextService.js.map +7 -0
- package/dist/modules/ecommerce/lib/storeLocale.js +60 -0
- package/dist/modules/ecommerce/lib/storeLocale.js.map +7 -0
- package/dist/modules/ecommerce/lib/storeNotifications.js +41 -0
- package/dist/modules/ecommerce/lib/storeNotifications.js.map +7 -0
- package/dist/modules/ecommerce/lib/storefrontCatalogSupport.js +248 -0
- package/dist/modules/ecommerce/lib/storefrontCatalogSupport.js.map +7 -0
- package/dist/modules/ecommerce/lib/storefrontCategories.js +241 -0
- package/dist/modules/ecommerce/lib/storefrontCategories.js.map +7 -0
- package/dist/modules/ecommerce/lib/storefrontCategoryCache.js +71 -0
- package/dist/modules/ecommerce/lib/storefrontCategoryCache.js.map +7 -0
- package/dist/modules/ecommerce/lib/storefrontDetail.js +447 -0
- package/dist/modules/ecommerce/lib/storefrontDetail.js.map +7 -0
- package/dist/modules/ecommerce/lib/storefrontFacets.js +441 -0
- package/dist/modules/ecommerce/lib/storefrontFacets.js.map +7 -0
- package/dist/modules/ecommerce/lib/storefrontOptionFilter.js +15 -0
- package/dist/modules/ecommerce/lib/storefrontOptionFilter.js.map +7 -0
- package/dist/modules/ecommerce/lib/storefrontPricing.js +393 -0
- package/dist/modules/ecommerce/lib/storefrontPricing.js.map +7 -0
- package/dist/modules/ecommerce/lib/storefrontProductCache.js +90 -0
- package/dist/modules/ecommerce/lib/storefrontProductCache.js.map +7 -0
- package/dist/modules/ecommerce/lib/storefrontProductScope.js +107 -0
- package/dist/modules/ecommerce/lib/storefrontProductScope.js.map +7 -0
- package/dist/modules/ecommerce/lib/storefrontProducts.js +670 -0
- package/dist/modules/ecommerce/lib/storefrontProducts.js.map +7 -0
- package/dist/modules/ecommerce/lib/storefrontQuery.js +156 -0
- package/dist/modules/ecommerce/lib/storefrontQuery.js.map +7 -0
- package/dist/modules/ecommerce/lib/storefrontRateLimit.js +99 -0
- package/dist/modules/ecommerce/lib/storefrontRateLimit.js.map +7 -0
- package/dist/modules/ecommerce/lib/storefrontSearch.js +303 -0
- package/dist/modules/ecommerce/lib/storefrontSearch.js.map +7 -0
- package/dist/modules/ecommerce/lib/storefrontSearchCache.js +35 -0
- package/dist/modules/ecommerce/lib/storefrontSearchCache.js.map +7 -0
- package/dist/modules/ecommerce/lib/subscriberSupport.js +33 -0
- package/dist/modules/ecommerce/lib/subscriberSupport.js.map +7 -0
- package/dist/modules/ecommerce/lib/types.js +1 -0
- package/dist/modules/ecommerce/lib/types.js.map +7 -0
- package/dist/modules/ecommerce/migrations/Migration20261005113233_ecommerce.js +32 -0
- package/dist/modules/ecommerce/migrations/Migration20261005113233_ecommerce.js.map +7 -0
- package/dist/modules/ecommerce/migrations/Migration20261006112733_ecommerce.js +17 -0
- package/dist/modules/ecommerce/migrations/Migration20261006112733_ecommerce.js.map +7 -0
- package/dist/modules/ecommerce/notifications.client.js +47 -0
- package/dist/modules/ecommerce/notifications.client.js.map +7 -0
- package/dist/modules/ecommerce/notifications.js +48 -0
- package/dist/modules/ecommerce/notifications.js.map +7 -0
- package/dist/modules/ecommerce/search.js +83 -0
- package/dist/modules/ecommerce/search.js.map +7 -0
- package/dist/modules/ecommerce/setup.js +17 -0
- package/dist/modules/ecommerce/setup.js.map +7 -0
- package/dist/modules/ecommerce/subscribers/assortment-empty-notifications.js +14 -0
- package/dist/modules/ecommerce/subscribers/assortment-empty-notifications.js.map +7 -0
- package/dist/modules/ecommerce/subscribers/availability-policy-cache-invalidation.js +14 -0
- package/dist/modules/ecommerce/subscribers/availability-policy-cache-invalidation.js.map +7 -0
- package/dist/modules/ecommerce/subscribers/catalog-category-cache-invalidation.js +14 -0
- package/dist/modules/ecommerce/subscribers/catalog-category-cache-invalidation.js.map +7 -0
- package/dist/modules/ecommerce/subscribers/catalog-price-cache-invalidation.js +14 -0
- package/dist/modules/ecommerce/subscribers/catalog-price-cache-invalidation.js.map +7 -0
- package/dist/modules/ecommerce/subscribers/catalog-product-cache-invalidation.js +14 -0
- package/dist/modules/ecommerce/subscribers/catalog-product-cache-invalidation.js.map +7 -0
- package/dist/modules/ecommerce/subscribers/catalog-variant-cache-invalidation.js +14 -0
- package/dist/modules/ecommerce/subscribers/catalog-variant-cache-invalidation.js.map +7 -0
- package/dist/modules/ecommerce/subscribers/customer-group-cache-invalidation.js +14 -0
- package/dist/modules/ecommerce/subscribers/customer-group-cache-invalidation.js.map +7 -0
- package/dist/modules/ecommerce/subscribers/customer-group-membership-cache-invalidation.js +14 -0
- package/dist/modules/ecommerce/subscribers/customer-group-membership-cache-invalidation.js.map +7 -0
- package/dist/modules/ecommerce/subscribers/customer-group-terms-cache-invalidation.js +14 -0
- package/dist/modules/ecommerce/subscribers/customer-group-terms-cache-invalidation.js.map +7 -0
- package/dist/modules/ecommerce/subscribers/customer-user-deleted-cache-invalidation.js +14 -0
- package/dist/modules/ecommerce/subscribers/customer-user-deleted-cache-invalidation.js.map +7 -0
- package/dist/modules/ecommerce/subscribers/customer-user-updated-cache-invalidation.js +14 -0
- package/dist/modules/ecommerce/subscribers/customer-user-updated-cache-invalidation.js.map +7 -0
- package/dist/modules/ecommerce/subscribers/domain-binding-rebind.js +16 -0
- package/dist/modules/ecommerce/subscribers/domain-binding-rebind.js.map +7 -0
- package/dist/modules/ecommerce/subscribers/domain-mapping-cache-invalidation.js +14 -0
- package/dist/modules/ecommerce/subscribers/domain-mapping-cache-invalidation.js.map +7 -0
- package/dist/modules/ecommerce/subscribers/store-cache-invalidation.js +14 -0
- package/dist/modules/ecommerce/subscribers/store-cache-invalidation.js.map +7 -0
- package/dist/modules/ecommerce/subscribers/store-channel-binding-cache-invalidation.js +14 -0
- package/dist/modules/ecommerce/subscribers/store-channel-binding-cache-invalidation.js.map +7 -0
- package/dist/modules/ecommerce/subscribers/store-domain-binding-cache-invalidation.js +14 -0
- package/dist/modules/ecommerce/subscribers/store-domain-binding-cache-invalidation.js.map +7 -0
- package/dist/modules/ecommerce/subscribers/store-notifications.js +14 -0
- package/dist/modules/ecommerce/subscribers/store-notifications.js.map +7 -0
- package/dist/modules/query_index/lib/batch.js +23 -9
- package/dist/modules/query_index/lib/batch.js.map +2 -2
- package/dist/modules/query_index/lib/doc-enrichers.js +212 -0
- package/dist/modules/query_index/lib/doc-enrichers.js.map +7 -0
- package/dist/modules/query_index/lib/document.js +2 -0
- package/dist/modules/query_index/lib/document.js.map +2 -2
- package/dist/modules/query_index/lib/engine.js +68 -1
- package/dist/modules/query_index/lib/engine.js.map +2 -2
- package/dist/modules/query_index/lib/indexer.js +7 -0
- package/dist/modules/query_index/lib/indexer.js.map +2 -2
- package/dist/modules/query_index/lib/search-tokens.js +2 -0
- package/dist/modules/query_index/lib/search-tokens.js.map +2 -2
- package/dist/modules/query_index/migrations/Migration20261005201500_query_index.js +17 -0
- package/dist/modules/query_index/migrations/Migration20261005201500_query_index.js.map +7 -0
- package/dist/modules/translations/components/TranslationManager.js +3 -3
- package/dist/modules/translations/components/TranslationManager.js.map +2 -2
- package/dist/modules/translations/data/validators.js +1 -1
- package/dist/modules/translations/data/validators.js.map +1 -1
- package/dist/modules/translations/lib/batch.js +21 -1
- package/dist/modules/translations/lib/batch.js.map +2 -2
- package/dist/modules/translations/lib/helpers.js +3 -0
- package/dist/modules/translations/lib/helpers.js.map +2 -2
- package/dist/modules/translations/lib/resolve-field-list.js +14 -2
- package/dist/modules/translations/lib/resolve-field-list.js.map +2 -2
- package/generated/entities/catalog_price_history_entry/index.ts +25 -0
- package/generated/entities/customer_group_terms/index.ts +1 -0
- package/generated/entities/ecommerce_store/index.ts +15 -0
- package/generated/entities/ecommerce_store_channel_binding/index.ts +13 -0
- package/generated/entities/ecommerce_store_domain_binding/index.ts +10 -0
- package/generated/entities.ids.generated.ts +8 -1
- package/generated/entity-fields-registry.ts +72 -0
- package/package.json +7 -7
- package/src/helpers/integration/customerGroupsFixtures.ts +2 -0
- package/src/modules/auth/i18n/de.json +8 -0
- package/src/modules/auth/i18n/en.json +8 -0
- package/src/modules/auth/i18n/es.json +8 -0
- package/src/modules/auth/i18n/ko.json +8 -0
- package/src/modules/auth/i18n/pl.json +8 -0
- package/src/modules/catalog/AGENTS.md +5 -2
- package/src/modules/catalog/acl.ts +7 -0
- package/src/modules/catalog/api/config/omnibus/route.ts +204 -0
- package/src/modules/catalog/api/prices/history/route.ts +139 -0
- package/src/modules/catalog/api/prices/omnibus-preview/route.ts +202 -0
- package/src/modules/catalog/api/products/route.ts +53 -308
- package/src/modules/catalog/api/tags/route.ts +6 -0
- package/src/modules/catalog/backend/config/catalog/page.tsx +2 -0
- package/src/modules/catalog/cli.ts +84 -1
- package/src/modules/catalog/commands/categories.ts +54 -2
- package/src/modules/catalog/commands/prices.ts +43 -1
- package/src/modules/catalog/commands/products.ts +84 -1
- package/src/modules/catalog/commands/variants.ts +7 -0
- package/src/modules/catalog/components/OmnibusSettings.tsx +832 -0
- package/src/modules/catalog/components/PriceEditorOmnibusRow.tsx +219 -0
- package/src/modules/catalog/components/lookupSources.ts +29 -0
- package/src/modules/catalog/components/prices/PriceScopeSelectors.tsx +9 -0
- package/src/modules/catalog/components/products/VariantBuilder.tsx +9 -0
- package/src/modules/catalog/data/entities.ts +123 -1
- package/src/modules/catalog/data/validators.ts +118 -0
- package/src/modules/catalog/di.ts +23 -0
- package/src/modules/catalog/i18n/de.json +77 -0
- package/src/modules/catalog/i18n/en.json +77 -0
- package/src/modules/catalog/i18n/es.json +77 -0
- package/src/modules/catalog/i18n/ko.json +77 -0
- package/src/modules/catalog/i18n/pl.json +77 -0
- package/src/modules/catalog/lib/omnibus.ts +406 -0
- package/src/modules/catalog/lib/omnibusBackfill.ts +198 -0
- package/src/modules/catalog/lib/omnibusCache.ts +111 -0
- package/src/modules/catalog/lib/omnibusConfig.ts +73 -0
- package/src/modules/catalog/lib/omnibusHistoryQueries.ts +248 -0
- package/src/modules/catalog/lib/omnibusPresentedEntry.ts +76 -0
- package/src/modules/catalog/lib/omnibusProductListEnrichment.ts +125 -0
- package/src/modules/catalog/lib/omnibusTypes.ts +202 -0
- package/src/modules/catalog/lib/optionSchemaTranslations.ts +55 -0
- package/src/modules/catalog/lib/priceHistoryQuery.ts +155 -0
- package/src/modules/catalog/lib/pricing.ts +42 -4
- package/src/modules/catalog/lib/productFilters.ts +349 -0
- package/src/modules/catalog/lib/productScopeKeys.ts +162 -0
- package/src/modules/catalog/lib/productScopeReindex.ts +139 -0
- package/src/modules/catalog/migrations/.snapshot-open-mercato.json +572 -0
- package/src/modules/catalog/migrations/Migration20261005105352_catalog.ts +39 -0
- package/src/modules/catalog/migrations/Migration20261005150824_catalog.ts +39 -0
- package/src/modules/catalog/services/catalogOmnibusService.ts +642 -0
- package/src/modules/catalog/setup.ts +2 -0
- package/src/modules/catalog/subscribers/product-scope-keys-category-deleted.ts +18 -0
- package/src/modules/catalog/subscribers/product-scope-keys-category-updated.ts +19 -0
- package/src/modules/catalog/translations.ts +12 -0
- package/src/modules/configs/i18n/de.json +5 -1
- package/src/modules/configs/i18n/en.json +5 -1
- package/src/modules/configs/i18n/es.json +5 -1
- package/src/modules/configs/i18n/ko.json +5 -1
- package/src/modules/configs/i18n/pl.json +5 -1
- package/src/modules/configs/lib/upgrade-actions.ts +35 -0
- package/src/modules/customer_groups/api/customer-groups/[id]/terms/route.ts +7 -0
- package/src/modules/customer_groups/components/CustomerGroupTermsSection.tsx +124 -2
- package/src/modules/customer_groups/components/customerGroupAssortmentScope.ts +46 -0
- package/src/modules/customer_groups/data/entities.ts +4 -0
- package/src/modules/customer_groups/data/validators.ts +14 -0
- package/src/modules/customer_groups/i18n/de.json +12 -0
- package/src/modules/customer_groups/i18n/en.json +12 -0
- package/src/modules/customer_groups/i18n/es.json +12 -0
- package/src/modules/customer_groups/i18n/ko.json +12 -0
- package/src/modules/customer_groups/i18n/pl.json +12 -0
- package/src/modules/customer_groups/migrations/.snapshot-open-mercato.json +17 -0
- package/src/modules/customer_groups/migrations/Migration20261005110250_customer_groups.ts +15 -0
- package/src/modules/customer_groups/services/customerGroupsService.ts +74 -23
- package/src/modules/ecommerce/acl.ts +9 -0
- package/src/modules/ecommerce/api/domain-mappings/route.ts +76 -0
- package/src/modules/ecommerce/api/openapi.ts +28 -0
- package/src/modules/ecommerce/api/store-channel-bindings/[id]/assortment-count/route.ts +141 -0
- package/src/modules/ecommerce/api/store-channel-bindings/crud.ts +283 -0
- package/src/modules/ecommerce/api/store-channel-bindings/route.ts +67 -0
- package/src/modules/ecommerce/api/store-domain-bindings/crud.ts +324 -0
- package/src/modules/ecommerce/api/store-domain-bindings/route.ts +73 -0
- package/src/modules/ecommerce/api/storefront/categories/[slug]/route.ts +94 -0
- package/src/modules/ecommerce/api/storefront/categories/openapiSchemas.ts +63 -0
- package/src/modules/ecommerce/api/storefront/categories/route.ts +78 -0
- package/src/modules/ecommerce/api/storefront/context/route.ts +203 -0
- package/src/modules/ecommerce/api/storefront/products/[idOrHandle]/route.ts +94 -0
- package/src/modules/ecommerce/api/storefront/products/openapiSchemas.ts +193 -0
- package/src/modules/ecommerce/api/storefront/products/route.ts +81 -0
- package/src/modules/ecommerce/api/storefront/search/suggest/openapiSchemas.ts +25 -0
- package/src/modules/ecommerce/api/storefront/search/suggest/route.ts +78 -0
- package/src/modules/ecommerce/api/storefront/storefrontRouteSupport.ts +68 -0
- package/src/modules/ecommerce/api/stores/[id]/branding/route.ts +205 -0
- package/src/modules/ecommerce/api/stores/[id]/preview-branding/route.ts +99 -0
- package/src/modules/ecommerce/api/stores/crud.ts +457 -0
- package/src/modules/ecommerce/api/stores/route.ts +66 -0
- package/src/modules/ecommerce/backend/config/ecommerce/[id]/page.meta.ts +12 -0
- package/src/modules/ecommerce/backend/config/ecommerce/[id]/page.tsx +5 -0
- package/src/modules/ecommerce/backend/config/ecommerce/page.meta.ts +12 -0
- package/src/modules/ecommerce/backend/config/ecommerce/page.tsx +5 -0
- package/src/modules/ecommerce/commands/stores.ts +154 -0
- package/src/modules/ecommerce/components/ChannelAssortmentCount.tsx +138 -0
- package/src/modules/ecommerce/components/ChannelBindingFormSections.tsx +40 -0
- package/src/modules/ecommerce/components/ChannelLookupPicker.tsx +5 -0
- package/src/modules/ecommerce/components/DomainStatusWarning.tsx +32 -0
- package/src/modules/ecommerce/components/StoreAvailabilityDefaultsSection.tsx +219 -0
- package/src/modules/ecommerce/components/StoreBrandingControls.tsx +73 -0
- package/src/modules/ecommerce/components/StoreBrandingPreview.tsx +77 -0
- package/src/modules/ecommerce/components/StoreBrandingTab.tsx +242 -0
- package/src/modules/ecommerce/components/StoreChannelBindingDialog.tsx +306 -0
- package/src/modules/ecommerce/components/StoreChannelsTab.tsx +375 -0
- package/src/modules/ecommerce/components/StoreCreateDialog.tsx +204 -0
- package/src/modules/ecommerce/components/StoreDomainBindingDialog.tsx +198 -0
- package/src/modules/ecommerce/components/StoreDomainsTab.tsx +426 -0
- package/src/modules/ecommerce/components/StoreEditPage.tsx +193 -0
- package/src/modules/ecommerce/components/StoreGeneralTab.tsx +189 -0
- package/src/modules/ecommerce/components/StoreListPage.tsx +326 -0
- package/src/modules/ecommerce/components/StoreSeoTab.tsx +152 -0
- package/src/modules/ecommerce/components/StoreStatusBadge.tsx +22 -0
- package/src/modules/ecommerce/components/channelLookups.ts +24 -0
- package/src/modules/ecommerce/components/storeAdmin.ts +56 -0
- package/src/modules/ecommerce/components/storeBrandingForm.ts +230 -0
- package/src/modules/ecommerce/components/storeChannels.ts +210 -0
- package/src/modules/ecommerce/components/storeDomains.ts +221 -0
- package/src/modules/ecommerce/components/storeEditTabs.tsx +76 -0
- package/src/modules/ecommerce/components/storeGeneral.ts +155 -0
- package/src/modules/ecommerce/components/storeSeo.ts +78 -0
- package/src/modules/ecommerce/components/useChannelAssortmentCount.ts +67 -0
- package/src/modules/ecommerce/components/useStoreAccess.ts +36 -0
- package/src/modules/ecommerce/data/enrichers.ts +213 -0
- package/src/modules/ecommerce/data/entities.ts +168 -0
- package/src/modules/ecommerce/data/validators.ts +587 -0
- package/src/modules/ecommerce/di.ts +9 -0
- package/src/modules/ecommerce/events.ts +27 -0
- package/src/modules/ecommerce/i18n/de.json +341 -0
- package/src/modules/ecommerce/i18n/en.json +341 -0
- package/src/modules/ecommerce/i18n/es.json +341 -0
- package/src/modules/ecommerce/i18n/ko.json +341 -0
- package/src/modules/ecommerce/i18n/pl.json +341 -0
- package/src/modules/ecommerce/index.ts +14 -0
- package/src/modules/ecommerce/lib/assortmentCount.ts +175 -0
- package/src/modules/ecommerce/lib/brandingStyles.ts +215 -0
- package/src/modules/ecommerce/lib/buyerContext.ts +401 -0
- package/src/modules/ecommerce/lib/cacheInvalidation.ts +302 -0
- package/src/modules/ecommerce/lib/cacheKeys.ts +340 -0
- package/src/modules/ecommerce/lib/createConflictRecheck.ts +31 -0
- package/src/modules/ecommerce/lib/crudEvents.ts +128 -0
- package/src/modules/ecommerce/lib/crudSupport.ts +85 -0
- package/src/modules/ecommerce/lib/domainBindingRebind.ts +138 -0
- package/src/modules/ecommerce/lib/domainMappingSummaries.ts +71 -0
- package/src/modules/ecommerce/lib/exclusiveFlag.ts +90 -0
- package/src/modules/ecommerce/lib/references.ts +93 -0
- package/src/modules/ecommerce/lib/seedDraftStore.ts +125 -0
- package/src/modules/ecommerce/lib/storeBindingCascade.ts +138 -0
- package/src/modules/ecommerce/lib/storeBranding.ts +48 -0
- package/src/modules/ecommerce/lib/storeBrandingRoute.ts +91 -0
- package/src/modules/ecommerce/lib/storeContext.ts +669 -0
- package/src/modules/ecommerce/lib/storeContextService.ts +71 -0
- package/src/modules/ecommerce/lib/storeLocale.ts +88 -0
- package/src/modules/ecommerce/lib/storeNotifications.ts +55 -0
- package/src/modules/ecommerce/lib/storefrontCatalogSupport.ts +427 -0
- package/src/modules/ecommerce/lib/storefrontCategories.ts +419 -0
- package/src/modules/ecommerce/lib/storefrontCategoryCache.ts +113 -0
- package/src/modules/ecommerce/lib/storefrontDetail.ts +654 -0
- package/src/modules/ecommerce/lib/storefrontFacets.ts +697 -0
- package/src/modules/ecommerce/lib/storefrontOptionFilter.ts +44 -0
- package/src/modules/ecommerce/lib/storefrontPricing.ts +583 -0
- package/src/modules/ecommerce/lib/storefrontProductCache.ts +126 -0
- package/src/modules/ecommerce/lib/storefrontProductScope.ts +181 -0
- package/src/modules/ecommerce/lib/storefrontProducts.ts +963 -0
- package/src/modules/ecommerce/lib/storefrontQuery.ts +215 -0
- package/src/modules/ecommerce/lib/storefrontRateLimit.ts +126 -0
- package/src/modules/ecommerce/lib/storefrontSearch.ts +454 -0
- package/src/modules/ecommerce/lib/storefrontSearchCache.ts +49 -0
- package/src/modules/ecommerce/lib/subscriberSupport.ts +41 -0
- package/src/modules/ecommerce/lib/types.ts +69 -0
- package/src/modules/ecommerce/migrations/.snapshot-open-mercato.json +829 -0
- package/src/modules/ecommerce/migrations/Migration20261005113233_ecommerce.ts +32 -0
- package/src/modules/ecommerce/migrations/Migration20261006112733_ecommerce.ts +15 -0
- package/src/modules/ecommerce/notifications.client.ts +46 -0
- package/src/modules/ecommerce/notifications.ts +46 -0
- package/src/modules/ecommerce/search.ts +93 -0
- package/src/modules/ecommerce/setup.ts +15 -0
- package/src/modules/ecommerce/subscribers/assortment-empty-notifications.ts +12 -0
- package/src/modules/ecommerce/subscribers/availability-policy-cache-invalidation.ts +12 -0
- package/src/modules/ecommerce/subscribers/catalog-category-cache-invalidation.ts +12 -0
- package/src/modules/ecommerce/subscribers/catalog-price-cache-invalidation.ts +12 -0
- package/src/modules/ecommerce/subscribers/catalog-product-cache-invalidation.ts +12 -0
- package/src/modules/ecommerce/subscribers/catalog-variant-cache-invalidation.ts +12 -0
- package/src/modules/ecommerce/subscribers/customer-group-cache-invalidation.ts +12 -0
- package/src/modules/ecommerce/subscribers/customer-group-membership-cache-invalidation.ts +12 -0
- package/src/modules/ecommerce/subscribers/customer-group-terms-cache-invalidation.ts +12 -0
- package/src/modules/ecommerce/subscribers/customer-user-deleted-cache-invalidation.ts +12 -0
- package/src/modules/ecommerce/subscribers/customer-user-updated-cache-invalidation.ts +12 -0
- package/src/modules/ecommerce/subscribers/domain-binding-rebind.ts +14 -0
- package/src/modules/ecommerce/subscribers/domain-mapping-cache-invalidation.ts +12 -0
- package/src/modules/ecommerce/subscribers/store-cache-invalidation.ts +12 -0
- package/src/modules/ecommerce/subscribers/store-channel-binding-cache-invalidation.ts +12 -0
- package/src/modules/ecommerce/subscribers/store-domain-binding-cache-invalidation.ts +12 -0
- package/src/modules/ecommerce/subscribers/store-notifications.ts +12 -0
- package/src/modules/query_index/lib/batch.ts +25 -9
- package/src/modules/query_index/lib/doc-enrichers.ts +306 -0
- package/src/modules/query_index/lib/document.ts +2 -0
- package/src/modules/query_index/lib/engine.ts +58 -1
- package/src/modules/query_index/lib/indexer.ts +8 -0
- package/src/modules/query_index/lib/search-tokens.ts +2 -0
- package/src/modules/query_index/migrations/Migration20261005201500_query_index.ts +42 -0
- package/src/modules/translations/components/TranslationManager.tsx +3 -3
- package/src/modules/translations/data/validators.ts +1 -1
- package/src/modules/translations/lib/batch.ts +47 -0
- package/src/modules/translations/lib/helpers.ts +4 -0
- package/src/modules/translations/lib/resolve-field-list.ts +16 -2
|
@@ -0,0 +1,454 @@
|
|
|
1
|
+
import type { AwilixContainer } from 'awilix'
|
|
2
|
+
import type { EntityManager } from '@mikro-orm/postgresql'
|
|
3
|
+
import { findWithDecryption } from '@open-mercato/shared/lib/encryption/find'
|
|
4
|
+
import { createLogger } from '@open-mercato/shared/lib/logger'
|
|
5
|
+
import { sanitizeSearchTerm } from '@open-mercato/shared/lib/query/sanitizeSearchTerm'
|
|
6
|
+
import { SortDir, type QueryEngine } from '@open-mercato/shared/lib/query/types'
|
|
7
|
+
import { resolveSearchConfig } from '@open-mercato/shared/lib/search/config'
|
|
8
|
+
import { tokenizeText } from '@open-mercato/shared/lib/search/tokenize'
|
|
9
|
+
import { getTelemetryRuntime } from '@open-mercato/shared/lib/telemetry/runtime'
|
|
10
|
+
import type { SearchStrategy } from '@open-mercato/shared/modules/search'
|
|
11
|
+
import { E } from '#generated/entities.ids.generated'
|
|
12
|
+
import { CatalogProductVariant } from '@open-mercato/core/modules/catalog/data/entities'
|
|
13
|
+
import { scoreProductSearchRelevance } from '@open-mercato/core/modules/catalog/lib/productFilters'
|
|
14
|
+
import { compareCodeUnits } from '@open-mercato/core/modules/catalog/lib/productScopeKeys'
|
|
15
|
+
import {
|
|
16
|
+
ECOMMERCE_STOREFRONT_SUGGEST_MIN_QUERY_LENGTH,
|
|
17
|
+
type EcommerceStorefrontSearchSuggestQuery,
|
|
18
|
+
} from '../data/validators'
|
|
19
|
+
import {
|
|
20
|
+
STOREFRONT_CATEGORY_ENTITY_TYPE,
|
|
21
|
+
STOREFRONT_PRODUCT_ENTITY_TYPE,
|
|
22
|
+
loadStorefrontTranslations,
|
|
23
|
+
localeChain,
|
|
24
|
+
localize,
|
|
25
|
+
nonEmptyString,
|
|
26
|
+
referenceId,
|
|
27
|
+
resolvePublicDefaultMediaUrls,
|
|
28
|
+
resolveStorefrontAvailability,
|
|
29
|
+
storefrontAvailabilityKey,
|
|
30
|
+
toStorefrontAvailability,
|
|
31
|
+
tryResolve,
|
|
32
|
+
} from './storefrontCatalogSupport'
|
|
33
|
+
import { loadStorefrontVisibleCategories, type StorefrontVisibleCategory } from './storefrontCategories'
|
|
34
|
+
import { resolveStorefrontPrices, type StorefrontProductPricing } from './storefrontPricing'
|
|
35
|
+
import {
|
|
36
|
+
buildStorefrontProductScope,
|
|
37
|
+
buildStorefrontSearchIndexDocFilter,
|
|
38
|
+
composeStorefrontProductFilters,
|
|
39
|
+
type StorefrontProductScope,
|
|
40
|
+
} from './storefrontProductScope'
|
|
41
|
+
import { buildStorefrontProductFilterClause } from './storefrontProducts'
|
|
42
|
+
import type { StoreContext } from './types'
|
|
43
|
+
|
|
44
|
+
/**
|
|
45
|
+
* `GET /search/suggest` (Storefront Public API rev 4 §4.5, §8).
|
|
46
|
+
*
|
|
47
|
+
* Products are ranked by one backend, chosen per request by `selectStorefrontSearchBackend`:
|
|
48
|
+
* the `tokens` strategy or the `vector` strategy on the `pgvector` driver of `@open-mercato/search`
|
|
49
|
+
* (D19), each receiving `buildStorefrontSearchIndexDocFilter` so the assortment scope is evaluated
|
|
50
|
+
* inside the ranking query and a restricted buyer is never starved by out-of-scope hits (§8.2,
|
|
51
|
+
* R14); otherwise the Phase 1 `ILIKE` path (§8.1) over the scoped catalogue. Meilisearch,
|
|
52
|
+
* `chromadb` and `qdrant` cannot evaluate the scope and are never used. Whatever ranked them, the
|
|
53
|
+
* products are re-read through `buildStorefrontProductScope`, priced for the buyer, and hidden when
|
|
54
|
+
* their policy hides out-of-stock products, exactly like the listing page. The payload is the same
|
|
55
|
+
* for every backend (§8.3); facets are never computed.
|
|
56
|
+
*
|
|
57
|
+
* Categories match the localized name of the buyer's visible categories (the `/categories`
|
|
58
|
+
* visibility rule) and are independent of the backend. `suggestions` stays empty in this phase.
|
|
59
|
+
*/
|
|
60
|
+
|
|
61
|
+
export type StorefrontSearchBackendPreference = 'auto' | 'ilike' | 'tokens' | 'vector'
|
|
62
|
+
|
|
63
|
+
export type StorefrontSearchBackend = 'ilike' | 'tokens' | 'vector'
|
|
64
|
+
|
|
65
|
+
export type StorefrontSearchBackendSelection =
|
|
66
|
+
| { backend: 'ilike' }
|
|
67
|
+
| { backend: 'tokens' | 'vector'; strategy: SearchStrategy }
|
|
68
|
+
|
|
69
|
+
export type StorefrontSearchSuggestProduct = {
|
|
70
|
+
id: string
|
|
71
|
+
handle: string | null
|
|
72
|
+
title: string
|
|
73
|
+
defaultMediaUrl: string | null
|
|
74
|
+
formattedPrice: string | null
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
export type StorefrontSearchSuggestCategory = { id: string; name: string; slug: string | null }
|
|
78
|
+
|
|
79
|
+
export type StorefrontSearchSuggestResponse = {
|
|
80
|
+
products: StorefrontSearchSuggestProduct[]
|
|
81
|
+
categories: StorefrontSearchSuggestCategory[]
|
|
82
|
+
suggestions: string[]
|
|
83
|
+
effectiveLocale: string
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
export type SuggestStorefrontSearchOptions = {
|
|
87
|
+
/** Overrides `OM_ECOMMERCE_STOREFRONT_SEARCH`; intended for tests. */
|
|
88
|
+
backendPreference?: StorefrontSearchBackendPreference
|
|
89
|
+
date?: Date
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
/** `auto` (default): `tokens`, then `pgvector`, then `ILIKE`; any other value forces that backend or `ILIKE`. */
|
|
93
|
+
export const STOREFRONT_SEARCH_BACKEND_ENV = 'OM_ECOMMERCE_STOREFRONT_SEARCH'
|
|
94
|
+
|
|
95
|
+
/** `ILIKE` candidates read (title order) before relevance ranking picks the `limit` best. */
|
|
96
|
+
export const STOREFRONT_SUGGEST_ILIKE_WINDOW = 100
|
|
97
|
+
|
|
98
|
+
const AUTO_ORDER: readonly StorefrontSearchBackend[] = ['tokens', 'vector']
|
|
99
|
+
|
|
100
|
+
const logger = createLogger('ecommerce').child({ component: 'storefront-search' })
|
|
101
|
+
|
|
102
|
+
type StrategyRegistry = {
|
|
103
|
+
getStrategy(strategyId: string): SearchStrategy | undefined
|
|
104
|
+
isStrategyAvailable(strategyId: string): Promise<boolean>
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
type Runtime = {
|
|
108
|
+
container: AwilixContainer
|
|
109
|
+
ctx: StoreContext
|
|
110
|
+
em: EntityManager
|
|
111
|
+
queryEngine: QueryEngine
|
|
112
|
+
scope: StorefrontProductScope
|
|
113
|
+
decryptionScope: { tenantId: string; organizationId: string }
|
|
114
|
+
date: Date
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
type RankedProduct = { id: string; score: number }
|
|
118
|
+
|
|
119
|
+
type ProductRecord = {
|
|
120
|
+
id: string
|
|
121
|
+
title?: string | null
|
|
122
|
+
sku?: string | null
|
|
123
|
+
handle?: string | null
|
|
124
|
+
default_media_id?: string | null
|
|
125
|
+
default_media_url?: string | null
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
type OrderedProduct = { record: ProductRecord; score: number; relevance: number; title: string }
|
|
129
|
+
|
|
130
|
+
export function readStorefrontSearchBackendPreference(
|
|
131
|
+
value: string | undefined = process.env[STOREFRONT_SEARCH_BACKEND_ENV],
|
|
132
|
+
): StorefrontSearchBackendPreference {
|
|
133
|
+
const token = value?.trim().toLowerCase()
|
|
134
|
+
if (token === 'ilike' || token === 'tokens' || token === 'vector') return token
|
|
135
|
+
return 'auto'
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
function isStrategyRegistry(value: unknown): value is StrategyRegistry {
|
|
139
|
+
if (!value || typeof value !== 'object') return false
|
|
140
|
+
const candidate = value as Partial<StrategyRegistry>
|
|
141
|
+
return typeof candidate.getStrategy === 'function' && typeof candidate.isStrategyAvailable === 'function'
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
function tokensCanRank(term: string): boolean {
|
|
145
|
+
const config = resolveSearchConfig()
|
|
146
|
+
return config.enabled && tokenizeText(term, config).hashes.length > 0
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
async function isAvailable(registry: StrategyRegistry, strategyId: string): Promise<boolean> {
|
|
150
|
+
try {
|
|
151
|
+
return await registry.isStrategyAvailable(strategyId)
|
|
152
|
+
} catch {
|
|
153
|
+
return false
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
/**
|
|
158
|
+
* The ranking backend for one search term (§8.2, D19). A search strategy qualifies only when it is
|
|
159
|
+
* registered, available and declares `supportsIndexDocFilter` — the `tokens` strategy, and the
|
|
160
|
+
* `vector` strategy over `pgvector`; `tokens` additionally needs search enabled and a term that
|
|
161
|
+
* tokenizes (shorter than the minimum token length it does not). Anything else is `ILIKE`.
|
|
162
|
+
*/
|
|
163
|
+
export async function selectStorefrontSearchBackend(
|
|
164
|
+
container: AwilixContainer,
|
|
165
|
+
term: string,
|
|
166
|
+
preference: StorefrontSearchBackendPreference = readStorefrontSearchBackendPreference(),
|
|
167
|
+
): Promise<StorefrontSearchBackendSelection> {
|
|
168
|
+
if (preference === 'ilike') return { backend: 'ilike' }
|
|
169
|
+
const registry = tryResolve<unknown>(container, 'searchService')
|
|
170
|
+
if (!isStrategyRegistry(registry)) return { backend: 'ilike' }
|
|
171
|
+
const order = preference === 'auto' ? AUTO_ORDER : [preference]
|
|
172
|
+
for (const backend of order) {
|
|
173
|
+
if (backend === 'ilike') continue
|
|
174
|
+
const strategy = registry.getStrategy(backend)
|
|
175
|
+
if (!strategy || strategy.supportsIndexDocFilter !== true) continue
|
|
176
|
+
if (backend === 'tokens' && !tokensCanRank(term)) continue
|
|
177
|
+
if (!(await isAvailable(registry, backend))) continue
|
|
178
|
+
return { backend, strategy }
|
|
179
|
+
}
|
|
180
|
+
return { backend: 'ilike' }
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
function normalizeForMatch(value: string): string {
|
|
184
|
+
return value.normalize('NFKD').replace(/[̀-ͯ]/g, '').toLowerCase()
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
function compareOrdered(left: OrderedProduct, right: OrderedProduct): number {
|
|
188
|
+
return (
|
|
189
|
+
right.score - left.score ||
|
|
190
|
+
left.relevance - right.relevance ||
|
|
191
|
+
left.title.localeCompare(right.title) ||
|
|
192
|
+
compareCodeUnits(left.record.id, right.record.id)
|
|
193
|
+
)
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
async function rankWithStrategy(
|
|
197
|
+
runtime: Runtime,
|
|
198
|
+
strategy: SearchStrategy,
|
|
199
|
+
term: string,
|
|
200
|
+
limit: number,
|
|
201
|
+
): Promise<RankedProduct[]> {
|
|
202
|
+
const { ctx } = runtime
|
|
203
|
+
const results = await strategy.search(term, {
|
|
204
|
+
tenantId: ctx.tenantId,
|
|
205
|
+
organizationId: ctx.organizationId,
|
|
206
|
+
entityTypes: [E.catalog.catalog_product],
|
|
207
|
+
limit,
|
|
208
|
+
indexDocFilter: buildStorefrontSearchIndexDocFilter(ctx),
|
|
209
|
+
})
|
|
210
|
+
const seen = new Set<string>()
|
|
211
|
+
const ranked: RankedProduct[] = []
|
|
212
|
+
for (const result of results) {
|
|
213
|
+
if (result.entityId !== E.catalog.catalog_product || seen.has(result.recordId)) continue
|
|
214
|
+
seen.add(result.recordId)
|
|
215
|
+
ranked.push({ id: result.recordId, score: Number.isFinite(result.score) ? result.score : 0 })
|
|
216
|
+
}
|
|
217
|
+
return ranked.slice(0, limit)
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
async function rankWithIlike(runtime: Runtime, term: string, limit: number): Promise<RankedProduct[]> {
|
|
221
|
+
const { scope } = runtime
|
|
222
|
+
const clause = await buildStorefrontProductFilterClause(runtime.container, runtime.ctx, { search: term })
|
|
223
|
+
const result = await runtime.queryEngine.query<ProductRecord>(E.catalog.catalog_product, {
|
|
224
|
+
tenantId: scope.tenantId,
|
|
225
|
+
organizationId: scope.organizationId,
|
|
226
|
+
withDeleted: scope.withDeleted,
|
|
227
|
+
filters: composeStorefrontProductFilters(scope, clause),
|
|
228
|
+
fields: ['id', 'title', 'sku'],
|
|
229
|
+
sort: [{ field: 'title', dir: SortDir.Asc }],
|
|
230
|
+
page: { page: 1, pageSize: STOREFRONT_SUGGEST_ILIKE_WINDOW },
|
|
231
|
+
})
|
|
232
|
+
const needle = term.toLowerCase()
|
|
233
|
+
return result.items
|
|
234
|
+
.filter((item) => typeof item.id === 'string')
|
|
235
|
+
.map((record) => ({
|
|
236
|
+
record,
|
|
237
|
+
score: 0,
|
|
238
|
+
relevance: scoreProductSearchRelevance(needle, record.title, record.sku),
|
|
239
|
+
title: nonEmptyString(record.title) ?? '',
|
|
240
|
+
}))
|
|
241
|
+
.sort(compareOrdered)
|
|
242
|
+
.slice(0, limit)
|
|
243
|
+
.map((entry) => ({ id: entry.record.id, score: 0 }))
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
async function rankProducts(
|
|
247
|
+
runtime: Runtime,
|
|
248
|
+
selection: StorefrontSearchBackendSelection,
|
|
249
|
+
term: string,
|
|
250
|
+
limit: number,
|
|
251
|
+
): Promise<RankedProduct[]> {
|
|
252
|
+
if (selection.backend === 'ilike') return rankWithIlike(runtime, term, limit)
|
|
253
|
+
try {
|
|
254
|
+
return await rankWithStrategy(runtime, selection.strategy, term, limit)
|
|
255
|
+
} catch (err) {
|
|
256
|
+
logger.error('[internal] ecommerce storefront search backend failed; falling back to ILIKE', {
|
|
257
|
+
backend: selection.backend,
|
|
258
|
+
tenantId: runtime.ctx.tenantId,
|
|
259
|
+
storeId: runtime.ctx.store.id,
|
|
260
|
+
err,
|
|
261
|
+
})
|
|
262
|
+
getTelemetryRuntime()?.reportError(err, {
|
|
263
|
+
module: 'ecommerce',
|
|
264
|
+
code: 'ecommerce.storefront_search_backend_failed',
|
|
265
|
+
attributes: { backend: selection.backend },
|
|
266
|
+
})
|
|
267
|
+
return rankWithIlike(runtime, term, limit)
|
|
268
|
+
}
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
async function loadProductsByIds(runtime: Runtime, ids: string[]): Promise<ProductRecord[]> {
|
|
272
|
+
if (!ids.length) return []
|
|
273
|
+
const { scope } = runtime
|
|
274
|
+
const result = await runtime.queryEngine.query<ProductRecord>(E.catalog.catalog_product, {
|
|
275
|
+
tenantId: scope.tenantId,
|
|
276
|
+
organizationId: scope.organizationId,
|
|
277
|
+
withDeleted: scope.withDeleted,
|
|
278
|
+
filters: composeStorefrontProductFilters(scope, { id: { $in: ids } }),
|
|
279
|
+
fields: ['id', 'title', 'sku', 'handle', 'default_media_id', 'default_media_url'],
|
|
280
|
+
page: { page: 1, pageSize: ids.length },
|
|
281
|
+
})
|
|
282
|
+
return result.items.filter((item) => typeof item.id === 'string')
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
async function loadVariantIds(runtime: Runtime, productIds: string[]): Promise<Map<string, string[]>> {
|
|
286
|
+
const byProduct = new Map<string, string[]>()
|
|
287
|
+
if (!productIds.length) return byProduct
|
|
288
|
+
const { ctx } = runtime
|
|
289
|
+
const variants = await findWithDecryption(
|
|
290
|
+
runtime.em,
|
|
291
|
+
CatalogProductVariant,
|
|
292
|
+
{
|
|
293
|
+
tenantId: ctx.tenantId,
|
|
294
|
+
organizationId: ctx.organizationId,
|
|
295
|
+
deletedAt: null,
|
|
296
|
+
isActive: true,
|
|
297
|
+
product: { $in: productIds },
|
|
298
|
+
},
|
|
299
|
+
{ fields: ['id', 'product'] },
|
|
300
|
+
runtime.decryptionScope,
|
|
301
|
+
)
|
|
302
|
+
for (const variant of variants) {
|
|
303
|
+
const productId = referenceId(variant.product)
|
|
304
|
+
if (!productId) continue
|
|
305
|
+
byProduct.set(productId, [...(byProduct.get(productId) ?? []), variant.id])
|
|
306
|
+
}
|
|
307
|
+
return byProduct
|
|
308
|
+
}
|
|
309
|
+
|
|
310
|
+
async function loadPricing(runtime: Runtime, productIds: string[]): Promise<Map<string, StorefrontProductPricing>> {
|
|
311
|
+
if (!productIds.length) return new Map()
|
|
312
|
+
const variants = await loadVariantIds(runtime, productIds)
|
|
313
|
+
return resolveStorefrontPrices(
|
|
314
|
+
runtime.container,
|
|
315
|
+
runtime.ctx,
|
|
316
|
+
productIds.map((productId) => ({ productId, variantIds: variants.get(productId) ?? [] })),
|
|
317
|
+
{ date: runtime.date },
|
|
318
|
+
)
|
|
319
|
+
}
|
|
320
|
+
|
|
321
|
+
async function loadHiddenProductIds(runtime: Runtime, productIds: string[]): Promise<Set<string>> {
|
|
322
|
+
const hidden = new Set<string>()
|
|
323
|
+
if (!productIds.length) return hidden
|
|
324
|
+
const targets = productIds.map((productId) => ({ productId, variantId: null }))
|
|
325
|
+
const resolved = await resolveStorefrontAvailability(runtime.container, runtime.ctx, runtime.em, targets)
|
|
326
|
+
for (const target of targets) {
|
|
327
|
+
const entry = resolved.get(storefrontAvailabilityKey(target))
|
|
328
|
+
if (entry?.policy?.hideWhenOutOfStock?.value !== true) continue
|
|
329
|
+
if (toStorefrontAvailability(entry.result ?? null).state === 'out_of_stock') hidden.add(target.productId)
|
|
330
|
+
}
|
|
331
|
+
return hidden
|
|
332
|
+
}
|
|
333
|
+
|
|
334
|
+
function formattedPrice(pricing: StorefrontProductPricing | undefined): string | null {
|
|
335
|
+
return pricing?.price?.formatted ?? pricing?.priceRange?.formattedMin ?? null
|
|
336
|
+
}
|
|
337
|
+
|
|
338
|
+
function matchCategories(
|
|
339
|
+
categories: Map<string, StorefrontVisibleCategory>,
|
|
340
|
+
translations: Map<string, Record<string, Record<string, unknown>>> | undefined,
|
|
341
|
+
locales: string[],
|
|
342
|
+
term: string,
|
|
343
|
+
limit: number,
|
|
344
|
+
): StorefrontSearchSuggestCategory[] {
|
|
345
|
+
const needle = normalizeForMatch(term)
|
|
346
|
+
const matches: Array<{ category: StorefrontSearchSuggestCategory; relevance: number }> = []
|
|
347
|
+
for (const row of categories.values()) {
|
|
348
|
+
const name = localize(row.name, translations?.get(row.id), 'name', locales) ?? row.name
|
|
349
|
+
const normalized = normalizeForMatch(name)
|
|
350
|
+
if (!normalized.includes(needle)) continue
|
|
351
|
+
matches.push({
|
|
352
|
+
category: { id: row.id, name, slug: row.slug },
|
|
353
|
+
relevance: scoreProductSearchRelevance(needle, normalized, null),
|
|
354
|
+
})
|
|
355
|
+
}
|
|
356
|
+
return matches
|
|
357
|
+
.sort(
|
|
358
|
+
(left, right) =>
|
|
359
|
+
left.relevance - right.relevance ||
|
|
360
|
+
left.category.name.localeCompare(right.category.name) ||
|
|
361
|
+
compareCodeUnits(left.category.id, right.category.id),
|
|
362
|
+
)
|
|
363
|
+
.slice(0, limit)
|
|
364
|
+
.map((entry) => entry.category)
|
|
365
|
+
}
|
|
366
|
+
|
|
367
|
+
function emptyResponse(ctx: StoreContext): StorefrontSearchSuggestResponse {
|
|
368
|
+
return { products: [], categories: [], suggestions: [], effectiveLocale: ctx.effectiveLocale }
|
|
369
|
+
}
|
|
370
|
+
|
|
371
|
+
/** The normalized term the suggest response is computed and cached for; `''` when shorter than the minimum. */
|
|
372
|
+
export function normalizeStorefrontSuggestTerm(q: string): string {
|
|
373
|
+
const term = sanitizeSearchTerm(q).replace(/\s+/g, ' ')
|
|
374
|
+
return Array.from(term).length >= ECOMMERCE_STOREFRONT_SUGGEST_MIN_QUERY_LENGTH ? term : ''
|
|
375
|
+
}
|
|
376
|
+
|
|
377
|
+
export async function suggestStorefrontSearch(
|
|
378
|
+
container: AwilixContainer,
|
|
379
|
+
ctx: StoreContext,
|
|
380
|
+
query: Pick<EcommerceStorefrontSearchSuggestQuery, 'q' | 'limit'>,
|
|
381
|
+
options: SuggestStorefrontSearchOptions = {},
|
|
382
|
+
): Promise<StorefrontSearchSuggestResponse> {
|
|
383
|
+
const term = normalizeStorefrontSuggestTerm(query.q)
|
|
384
|
+
const assortment = ctx.buyer.assortmentScope
|
|
385
|
+
if (!term || (assortment !== null && assortment.length === 0)) return emptyResponse(ctx)
|
|
386
|
+
const em = tryResolve<EntityManager>(container, 'em')
|
|
387
|
+
const queryEngine = tryResolve<QueryEngine>(container, 'queryEngine')
|
|
388
|
+
if (!em || !queryEngine) throw new Error('[internal] ecommerce storefront search requires em and queryEngine')
|
|
389
|
+
const runtime: Runtime = {
|
|
390
|
+
container,
|
|
391
|
+
ctx,
|
|
392
|
+
em,
|
|
393
|
+
queryEngine,
|
|
394
|
+
scope: buildStorefrontProductScope(ctx),
|
|
395
|
+
decryptionScope: { tenantId: ctx.tenantId, organizationId: ctx.organizationId },
|
|
396
|
+
date: options.date ?? new Date(),
|
|
397
|
+
}
|
|
398
|
+
const selection = await selectStorefrontSearchBackend(container, term, options.backendPreference)
|
|
399
|
+
const [ranked, categories] = await Promise.all([
|
|
400
|
+
rankProducts(runtime, selection, term, query.limit),
|
|
401
|
+
loadStorefrontVisibleCategories(runtime),
|
|
402
|
+
])
|
|
403
|
+
const rankedIds = ranked.map((entry) => entry.id)
|
|
404
|
+
const [records, translations, pricing, hidden] = await Promise.all([
|
|
405
|
+
loadProductsByIds(runtime, rankedIds),
|
|
406
|
+
loadStorefrontTranslations(em, ctx, [
|
|
407
|
+
{ entityType: STOREFRONT_PRODUCT_ENTITY_TYPE, ids: rankedIds },
|
|
408
|
+
{ entityType: STOREFRONT_CATEGORY_ENTITY_TYPE, ids: Array.from(categories.keys()) },
|
|
409
|
+
]),
|
|
410
|
+
loadPricing(runtime, rankedIds),
|
|
411
|
+
loadHiddenProductIds(runtime, rankedIds),
|
|
412
|
+
])
|
|
413
|
+
const visibleRecords = records.filter((record) => !hidden.has(record.id))
|
|
414
|
+
const mediaUrls = await resolvePublicDefaultMediaUrls(
|
|
415
|
+
em,
|
|
416
|
+
ctx,
|
|
417
|
+
visibleRecords.map((record) => ({
|
|
418
|
+
id: record.id,
|
|
419
|
+
defaultMediaId: record.default_media_id,
|
|
420
|
+
defaultMediaUrl: record.default_media_url,
|
|
421
|
+
})),
|
|
422
|
+
)
|
|
423
|
+
const locales = localeChain(ctx)
|
|
424
|
+
const productTranslations = translations.get(STOREFRONT_PRODUCT_ENTITY_TYPE)
|
|
425
|
+
const scores = new Map(ranked.map((entry) => [entry.id, entry.score]))
|
|
426
|
+
const needle = term.toLowerCase()
|
|
427
|
+
const products = visibleRecords
|
|
428
|
+
.map((record) => ({
|
|
429
|
+
record,
|
|
430
|
+
score: scores.get(record.id) ?? 0,
|
|
431
|
+
relevance: scoreProductSearchRelevance(needle, record.title, record.sku),
|
|
432
|
+
title: nonEmptyString(record.title) ?? '',
|
|
433
|
+
}))
|
|
434
|
+
.sort(compareOrdered)
|
|
435
|
+
.map(({ record }) => ({
|
|
436
|
+
id: record.id,
|
|
437
|
+
handle: nonEmptyString(record.handle),
|
|
438
|
+
title: localize(record.title, productTranslations?.get(record.id), 'title', locales) ?? '',
|
|
439
|
+
defaultMediaUrl: mediaUrls.get(record.id) ?? null,
|
|
440
|
+
formattedPrice: formattedPrice(pricing.get(record.id)),
|
|
441
|
+
}))
|
|
442
|
+
return {
|
|
443
|
+
products,
|
|
444
|
+
categories: matchCategories(
|
|
445
|
+
categories,
|
|
446
|
+
translations.get(STOREFRONT_CATEGORY_ENTITY_TYPE),
|
|
447
|
+
locales,
|
|
448
|
+
term,
|
|
449
|
+
query.limit,
|
|
450
|
+
),
|
|
451
|
+
suggestions: [],
|
|
452
|
+
effectiveLocale: ctx.effectiveLocale,
|
|
453
|
+
}
|
|
454
|
+
}
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
import type { AwilixContainer } from 'awilix'
|
|
2
|
+
import type { EcommerceStorefrontSearchSuggestQuery } from '../data/validators'
|
|
3
|
+
import { catalogProductsTag, storefrontAvailabilityTag, storefrontCache, storefrontCacheValueHash } from './cacheKeys'
|
|
4
|
+
import {
|
|
5
|
+
normalizeStorefrontSuggestTerm,
|
|
6
|
+
suggestStorefrontSearch,
|
|
7
|
+
type StorefrontSearchSuggestResponse,
|
|
8
|
+
} from './storefrontSearch'
|
|
9
|
+
import type { StoreContext } from './types'
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* Server-side cache for `GET /search/suggest` (Storefront Public API rev 4 §9): `formattedPrice` is
|
|
13
|
+
* buyer-priced, so entries are keyed on the full `digest` plus the normalized term and `limit`.
|
|
14
|
+
* They carry the tenant's `catalog-products` tag (every product, variant, category and price event)
|
|
15
|
+
* and the availability tag (out-of-stock hiding). A term below the minimum length is answered
|
|
16
|
+
* empty without touching the cache.
|
|
17
|
+
*/
|
|
18
|
+
|
|
19
|
+
export const STOREFRONT_SEARCH_SUGGEST_TTL_MS = 30_000
|
|
20
|
+
|
|
21
|
+
const SUGGEST_CACHE_SEGMENT = 'search-suggest'
|
|
22
|
+
|
|
23
|
+
export function storefrontSearchSuggestCacheParts(term: string, limit: number): string[] {
|
|
24
|
+
return [SUGGEST_CACHE_SEGMENT, storefrontCacheValueHash({ term: term.toLowerCase(), limit })]
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
export function storefrontSearchSuggestCacheTags(ctx: StoreContext): string[] {
|
|
28
|
+
return [catalogProductsTag(ctx.tenantId), storefrontAvailabilityTag(ctx.tenantId)]
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
export async function cachedSuggestStorefrontSearch(
|
|
32
|
+
container: AwilixContainer,
|
|
33
|
+
ctx: StoreContext,
|
|
34
|
+
query: Pick<EcommerceStorefrontSearchSuggestQuery, 'q' | 'limit'>,
|
|
35
|
+
): Promise<StorefrontSearchSuggestResponse> {
|
|
36
|
+
const term = normalizeStorefrontSuggestTerm(query.q)
|
|
37
|
+
if (!term) return suggestStorefrontSearch(container, ctx, query)
|
|
38
|
+
const cache = storefrontCache(container, ctx)
|
|
39
|
+
const parts = storefrontSearchSuggestCacheParts(term, query.limit)
|
|
40
|
+
const cached = await cache.get<StorefrontSearchSuggestResponse>(parts, { scope: 'digest' })
|
|
41
|
+
if (cached) return cached
|
|
42
|
+
const response = await suggestStorefrontSearch(container, ctx, { q: term, limit: query.limit })
|
|
43
|
+
await cache.set(parts, response, {
|
|
44
|
+
scope: 'digest',
|
|
45
|
+
ttlMs: STOREFRONT_SEARCH_SUGGEST_TTL_MS,
|
|
46
|
+
tags: storefrontSearchSuggestCacheTags(ctx),
|
|
47
|
+
})
|
|
48
|
+
return response
|
|
49
|
+
}
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
export type EcommerceSubscriberContext = {
|
|
2
|
+
resolve: <T = unknown>(name: string) => T
|
|
3
|
+
eventName?: string
|
|
4
|
+
tenantId?: string | null
|
|
5
|
+
organizationId?: string | null
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
export type EventScope = { tenantId: string | null; organizationId: string | null }
|
|
9
|
+
|
|
10
|
+
export function asPayloadRecord(payload: unknown): Record<string, unknown> {
|
|
11
|
+
return typeof payload === 'object' && payload !== null && !Array.isArray(payload)
|
|
12
|
+
? (payload as Record<string, unknown>)
|
|
13
|
+
: {}
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
export function readPayloadString(payload: Record<string, unknown>, key: string): string | null {
|
|
17
|
+
const value = payload[key]
|
|
18
|
+
return typeof value === 'string' && value.length > 0 ? value : null
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
/** Trusted emitter scope first (events AGENTS.md), payload fields only as a fallback. */
|
|
22
|
+
export function readEventScope(payload: Record<string, unknown>, ctx: EcommerceSubscriberContext): EventScope {
|
|
23
|
+
return {
|
|
24
|
+
tenantId: ctx.tenantId ?? readPayloadString(payload, 'tenantId'),
|
|
25
|
+
organizationId: ctx.organizationId ?? readPayloadString(payload, 'organizationId'),
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
export function eventAction(eventName: string | undefined): string | null {
|
|
30
|
+
if (!eventName) return null
|
|
31
|
+
const segments = eventName.split('.')
|
|
32
|
+
return segments.length > 0 ? segments[segments.length - 1] : null
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
export function tryResolveService<T>(ctx: Pick<EcommerceSubscriberContext, 'resolve'>, name: string): T | null {
|
|
36
|
+
try {
|
|
37
|
+
return ctx.resolve<T | null | undefined>(name) ?? null
|
|
38
|
+
} catch {
|
|
39
|
+
return null
|
|
40
|
+
}
|
|
41
|
+
}
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
import type { EffectiveAssortmentScope } from '@open-mercato/shared/lib/catalog-visibility'
|
|
2
|
+
import type { EcommercePriceDisplayMode, EcommercePriceSortFallback, EcommerceStoreSettings } from '../data/validators'
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Suite-wide buyer shape (SPEC-029 v4.6 §6, §6.0). Identity ids are read fresh from
|
|
6
|
+
* `customer_accounts.CustomerUser`, never from JWT claims.
|
|
7
|
+
*/
|
|
8
|
+
export type BuyerContext = {
|
|
9
|
+
/** `customer_accounts.CustomerUser.id` (auth.sub); null for anonymous buyers. */
|
|
10
|
+
customerUserId: string | null
|
|
11
|
+
/** `personEntityId ?? customerEntityId` — the most specific identity ("person wins"). */
|
|
12
|
+
customerId: string | null
|
|
13
|
+
/** `CustomerUser.customer_entity_id` — always the company. */
|
|
14
|
+
companyId: string | null
|
|
15
|
+
/** Distinct non-null `[personEntityId, customerEntityId]`, person first (D3). */
|
|
16
|
+
customerIds: string[]
|
|
17
|
+
/** Priority-ordered union over `customerIds` from `customerGroupsService`. */
|
|
18
|
+
customerGroupIds: string[]
|
|
19
|
+
isAuthenticated: boolean
|
|
20
|
+
/** Derived from the resolved price kind's `displayMode` (§6.1a); never set independently. */
|
|
21
|
+
taxMode: EcommercePriceDisplayMode
|
|
22
|
+
/** Group terms override the channel default; passed to `PricingContext` as a filter (D2). */
|
|
23
|
+
priceKindId: string | null
|
|
24
|
+
allowPurchaseOnAccount: boolean
|
|
25
|
+
approvalRequiredAbove: number | null
|
|
26
|
+
/** `intersectScopes(channel, group union)`; null = unrestricted, [] = deny-all. */
|
|
27
|
+
assortmentScope: EffectiveAssortmentScope
|
|
28
|
+
/** `hashEffectiveScope(assortmentScope)` — digest of the canonicalized resolved scope, never of its inputs. */
|
|
29
|
+
assortmentScopeHash: string
|
|
30
|
+
/** Truncated sha256 of channelId, currencyCode, priceKindId and sorted customerGroupIds. */
|
|
31
|
+
priceScopeKey: string
|
|
32
|
+
/** Null when no id in `customerIds` has its own price rows; otherwise those ids, person first, joined with ','. */
|
|
33
|
+
customerOverlayId: string | null
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
export type StoreContextStore = {
|
|
37
|
+
id: string
|
|
38
|
+
code: string
|
|
39
|
+
name: string
|
|
40
|
+
slug: string
|
|
41
|
+
status: 'active'
|
|
42
|
+
defaultLocale: string
|
|
43
|
+
supportedLocales: string[]
|
|
44
|
+
defaultCurrencyCode: string
|
|
45
|
+
settings: EcommerceStoreSettings
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
export type StoreContextChannel = {
|
|
49
|
+
channelBindingId: string
|
|
50
|
+
salesChannelId: string
|
|
51
|
+
priceKindId: string | null
|
|
52
|
+
priceSortFallback: EcommercePriceSortFallback
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
export type StoreContext = {
|
|
56
|
+
store: StoreContextStore
|
|
57
|
+
tenantId: string
|
|
58
|
+
organizationId: string
|
|
59
|
+
channel: StoreContextChannel | null
|
|
60
|
+
buyer: BuyerContext
|
|
61
|
+
effectiveLocale: string
|
|
62
|
+
requestedLocale: string | null
|
|
63
|
+
currencyCode: string
|
|
64
|
+
/**
|
|
65
|
+
* Stable digest of every field that can change what a buyer sees or pays (§6.1).
|
|
66
|
+
* MUST be a component of every buyer-dependent cache key derived from this context.
|
|
67
|
+
*/
|
|
68
|
+
digest: string
|
|
69
|
+
}
|