@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,7 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": 3,
|
|
3
|
+
"sources": ["../../../../src/modules/ecommerce/lib/buyerContext.ts"],
|
|
4
|
+
"sourcesContent": ["import { createHash } from 'node:crypto'\nimport type { EntityManager } from '@mikro-orm/postgresql'\nimport {\n hashEffectiveScope,\n intersectScopes,\n type AssortmentScope,\n type EffectiveAssortmentScope,\n} from '@open-mercato/shared/lib/catalog-visibility'\nimport { findOneWithDecryption } from '@open-mercato/shared/lib/encryption/find'\nimport { createLogger } from '@open-mercato/shared/lib/logger'\nimport { getTelemetryRuntime } from '@open-mercato/shared/lib/telemetry/runtime'\nimport { CustomerUser } from '@open-mercato/core/modules/customer_accounts/data/entities'\nimport { CatalogPriceKind } from '@open-mercato/core/modules/catalog/data/entities'\nimport type { CustomerGroupsService } from '@open-mercato/core/modules/customer_groups/services/customerGroupsService'\nimport type { EcommercePriceDisplayMode } from '../data/validators'\nimport { emitEcommerceEvent } from '../events'\nimport { buyerContextCache, ecommerceResolutionCache, type CacheContainer } from './cacheKeys'\nimport { StorefrontResolutionError, type ResolvedStore } from './storeContext'\nimport type { BuyerContext, StoreContext } from './types'\n\n/**\n * Buyer layer of storefront resolution (SPEC-029 v4.6 \u00A74.1 step 6, \u00A76, \u00A76.0, \u00A76.1, \u00A76.1a, \u00A78).\n *\n * The portal session only proves WHO is asking; identity ids are read fresh from\n * `customer_accounts.CustomerUser` (D3), never from JWT claims. A session issued for another\n * tenant or organization answers `401` (D4). The buyer layer is locale-independent and cached\n * for 60 s per (store, customerUserId | anonymous); `digest` is computed per request by\n * `composeStoreContext` because it includes the effective locale.\n */\n\nexport type BuyerContextContainer = CacheContainer\n\nexport const ASSORTMENT_EMPTY_THROTTLE_MS = 3_600_000\n\nconst DIGEST_LENGTH = 16\nconst NULL_SEGMENT = '-'\n\nconst logger = createLogger('ecommerce').child({ component: 'buyer-context' })\n\ntype BuyerIdentity = Pick<BuyerContext, 'customerUserId' | 'customerId' | 'companyId' | 'customerIds' | 'isAuthenticated'>\n\ntype CustomerOverlayDatabase = {\n catalog_product_variant_prices: {\n customer_id: string | null\n tenant_id: string\n organization_id: string\n }\n}\n\nconst ANONYMOUS_IDENTITY: BuyerIdentity = {\n customerUserId: null,\n customerId: null,\n companyId: null,\n customerIds: [],\n isAuthenticated: false,\n}\n\nfunction tryResolve<T>(container: BuyerContextContainer, name: string): T | null {\n try {\n const resolved = container.resolve(name) as T | null | undefined\n return resolved ?? null\n } catch {\n return null\n }\n}\n\nfunction requireService<T>(container: BuyerContextContainer, name: string): T {\n const service = tryResolve<T>(container, name)\n if (!service) throw new Error(`[internal] ecommerce buyer context requires the DI service ${name}`)\n return service\n}\n\nfunction sha256Truncated(segments: string[]): string {\n return createHash('sha256').update(segments.join('|')).digest('hex').slice(0, DIGEST_LENGTH)\n}\n\nexport function computePriceScopeKey(input: {\n channelId: string\n currencyCode: string\n priceKindId: string | null\n customerGroupIds: string[]\n}): string {\n const sortedGroupIds = [...input.customerGroupIds].sort((left, right) => (left < right ? -1 : left > right ? 1 : 0))\n return sha256Truncated([\n input.channelId,\n input.currencyCode,\n input.priceKindId ?? NULL_SEGMENT,\n sortedGroupIds.join(','),\n ])\n}\n\nexport function computeStoreContextDigest(input: {\n storeId: string\n effectiveLocale: string\n taxMode: EcommercePriceDisplayMode\n priceScopeKey: string\n assortmentScopeHash: string\n customerOverlayId: string | null\n}): string {\n return sha256Truncated([\n input.storeId,\n input.effectiveLocale,\n input.taxMode,\n input.priceScopeKey,\n input.assortmentScopeHash,\n input.customerOverlayId ?? NULL_SEGMENT,\n ])\n}\n\nexport function buyerIdentityFromCustomerUser(user: {\n id: string\n personEntityId?: string | null\n customerEntityId?: string | null\n}): BuyerIdentity {\n const personId = user.personEntityId ?? null\n const companyId = user.customerEntityId ?? null\n const customerIds = Array.from(\n new Set([personId, companyId].filter((id): id is string => typeof id === 'string' && id.length > 0)),\n )\n return {\n customerUserId: user.id,\n customerId: personId ?? companyId,\n companyId,\n customerIds,\n isAuthenticated: true,\n }\n}\n\nasync function loadBuyerIdentity(em: EntityManager, customerUserId: string, store: ResolvedStore): Promise<BuyerIdentity> {\n const scope = { tenantId: store.tenantId, organizationId: store.organizationId }\n const user = await findOneWithDecryption(\n em,\n CustomerUser,\n { id: customerUserId, ...scope, isActive: true, deletedAt: null },\n undefined,\n scope,\n )\n if (!user) {\n logger.debug('Portal session points at a missing or inactive customer user', { storeId: store.store.id })\n throw new StorefrontResolutionError(401, 'portal_session_invalid')\n }\n return buyerIdentityFromCustomerUser(user)\n}\n\nexport function taxModeFromPriceKindDisplayMode(displayMode: string | null | undefined): EcommercePriceDisplayMode | null {\n if (displayMode === 'excluding-tax') return 'net'\n if (displayMode === 'including-tax') return 'gross'\n return null\n}\n\nasync function resolveTaxMode(\n em: EntityManager,\n priceKindId: string | null,\n store: ResolvedStore,\n): Promise<EcommercePriceDisplayMode> {\n const fallback = store.store.settings.display.priceDisplayModeDefault\n if (!priceKindId) return fallback\n const scope = { tenantId: store.tenantId, organizationId: store.organizationId }\n const priceKind = await findOneWithDecryption(\n em,\n CatalogPriceKind,\n {\n id: priceKindId,\n tenantId: store.tenantId,\n deletedAt: null,\n $or: [{ organizationId: null }, { organizationId: store.organizationId }],\n },\n undefined,\n scope,\n )\n return taxModeFromPriceKindDisplayMode(priceKind?.displayMode) ?? fallback\n}\n\nasync function resolveCustomerOverlayId(\n em: EntityManager,\n customerIds: string[],\n store: ResolvedStore,\n): Promise<string | null> {\n if (customerIds.length === 0) return null\n const rows = await em\n .getKysely<CustomerOverlayDatabase>()\n .selectFrom('catalog_product_variant_prices')\n .select('customer_id')\n .distinct()\n .where('customer_id', 'in', customerIds)\n .where('tenant_id', '=', store.tenantId)\n .where('organization_id', '=', store.organizationId)\n .execute()\n const withOwnRows = new Set(rows.map((row) => row.customer_id).filter((id): id is string => typeof id === 'string'))\n const overlayIds = customerIds.filter((id) => withOwnRows.has(id))\n return overlayIds.length > 0 ? overlayIds.join(',') : null\n}\n\n/**\n * Effective assortment for this buyer: the customer-groups OR-list (groups plus the customer's\n * own override) AND-ed with the channel scope.\n */\nasync function resolveEffectiveAssortment(\n service: CustomerGroupsService,\n tenantId: string,\n channelAssortmentScope: AssortmentScope | null,\n identity: BuyerIdentity,\n): Promise<EffectiveAssortmentScope> {\n const buyerScope = await service.resolveAssortmentScope({\n customerId: identity.customerId,\n customerIds: identity.customerIds,\n tenantId,\n })\n return intersectScopes(channelAssortmentScope, buyerScope.scope)\n}\n\n/**\n * The effective assortment an anonymous buyer gets on a channel, resolved exactly as\n * `resolveBuyerContext` resolves it: a channel that requires authentication is deny-all (`[]`),\n * otherwise the channel scope intersected with the default customer group's scope. Lets the admin\n * assortment count reuse the storefront's resolution instead of re-deriving it.\n */\nexport async function resolveAnonymousAssortmentScope(\n container: BuyerContextContainer,\n input: { tenantId: string; channelAssortmentScope: AssortmentScope | null; requireAuthentication: boolean },\n): Promise<EffectiveAssortmentScope> {\n if (input.requireAuthentication) return []\n const service = requireService<CustomerGroupsService>(container, 'customerGroupsService')\n return resolveEffectiveAssortment(service, input.tenantId, input.channelAssortmentScope, ANONYMOUS_IDENTITY)\n}\n\nasync function reportEmptyAssortment(\n container: BuyerContextContainer,\n store: ResolvedStore,\n assortmentScopeHash: string,\n): Promise<void> {\n const cache = ecommerceResolutionCache(container)\n const key = ['assortment-empty', store.store.id, store.channel.channelBindingId, assortmentScopeHash]\n const alreadyReported = await cache.get<{ reportedAt: string }>(key)\n if (alreadyReported) return\n await cache.set(key, { reportedAt: new Date().toISOString() }, { ttlMs: ASSORTMENT_EMPTY_THROTTLE_MS })\n logger.warn('Authenticated buyer resolved to an empty assortment', {\n storeId: store.store.id,\n channelBindingId: store.channel.channelBindingId,\n assortmentScopeHash,\n })\n try {\n await emitEcommerceEvent(\n 'ecommerce.assortment.empty_detected',\n {\n id: store.store.id,\n storeId: store.store.id,\n channelBindingId: store.channel.channelBindingId,\n assortmentScopeHash,\n tenantId: store.tenantId,\n organizationId: store.organizationId,\n },\n { persistent: true, tenantId: store.tenantId, organizationId: store.organizationId },\n )\n } catch (error) {\n logger.warn('Failed to emit empty assortment event', { storeId: store.store.id, err: error })\n getTelemetryRuntime()?.reportError(error, {\n module: 'ecommerce',\n code: 'ecommerce.assortment_empty_emit_failed',\n attributes: { storeId: store.store.id },\n })\n }\n}\n\ntype BuyerGroupLayer = {\n customerGroupIds: string[]\n priceKindId: string | null\n allowPurchaseOnAccount: boolean\n approvalRequiredAbove: number | null\n assortmentScope: EffectiveAssortmentScope\n}\n\nfunction isClosedToBuyer(store: ResolvedStore, identity: BuyerIdentity): boolean {\n return store.channel.requireAuthentication && !identity.isAuthenticated\n}\n\nasync function resolveBuyerGroupLayer(\n container: BuyerContextContainer,\n store: ResolvedStore,\n identity: BuyerIdentity,\n): Promise<BuyerGroupLayer> {\n if (isClosedToBuyer(store, identity)) {\n return {\n customerGroupIds: [],\n priceKindId: store.channel.priceKindId ?? null,\n allowPurchaseOnAccount: false,\n approvalRequiredAbove: null,\n assortmentScope: [],\n }\n }\n const service = requireService<CustomerGroupsService>(container, 'customerGroupsService')\n const groups = await service.resolveGroups({\n customerId: identity.customerId,\n customerIds: identity.customerIds,\n tenantId: store.tenantId,\n })\n const terms = await service.resolveTerms({\n customerId: identity.customerId,\n customerIds: identity.customerIds,\n tenantId: store.tenantId,\n groupIds: groups.groupIds,\n })\n return {\n customerGroupIds: groups.groupIds,\n priceKindId: terms.priceKindId ?? store.channel.priceKindId ?? null,\n allowPurchaseOnAccount: terms.allowPurchaseOnAccount,\n approvalRequiredAbove: terms.approvalRequiredAbove,\n assortmentScope: await resolveEffectiveAssortment(\n service,\n store.tenantId,\n store.channel.assortmentScope,\n identity,\n ),\n }\n}\n\nasync function buildBuyerContext(\n container: BuyerContextContainer,\n store: ResolvedStore,\n identity: BuyerIdentity,\n): Promise<BuyerContext> {\n const em = requireService<EntityManager>(container, 'em')\n const { customerGroupIds, priceKindId, allowPurchaseOnAccount, approvalRequiredAbove, assortmentScope } =\n await resolveBuyerGroupLayer(container, store, identity)\n const taxMode = await resolveTaxMode(em, priceKindId, store)\n const assortmentScopeHash = hashEffectiveScope(assortmentScope)\n if (identity.isAuthenticated && Array.isArray(assortmentScope) && assortmentScope.length === 0) {\n await reportEmptyAssortment(container, store, assortmentScopeHash)\n }\n const customerOverlayId = await resolveCustomerOverlayId(em, identity.customerIds, store)\n return {\n ...identity,\n customerGroupIds,\n taxMode,\n priceKindId,\n allowPurchaseOnAccount,\n approvalRequiredAbove,\n assortmentScope,\n assortmentScopeHash,\n priceScopeKey: computePriceScopeKey({\n channelId: store.channel.salesChannelId,\n currencyCode: store.currencyCode,\n priceKindId,\n customerGroupIds,\n }),\n customerOverlayId,\n }\n}\n\nasync function readPortalSession(request: Request) {\n const { getCustomerAuthFromRequest } = await import('@open-mercato/core/modules/customer_accounts/lib/customerAuth')\n return getCustomerAuthFromRequest(request)\n}\n\nexport async function resolveBuyerContext(\n container: BuyerContextContainer,\n store: ResolvedStore,\n request: Request | null,\n): Promise<BuyerContext> {\n const auth = request ? await readPortalSession(request) : null\n if (auth && (auth.tenantId !== store.tenantId || auth.orgId !== store.organizationId)) {\n logger.debug('Portal session scope does not match the resolved store', { storeId: store.store.id })\n throw new StorefrontResolutionError(401, 'portal_session_scope_mismatch')\n }\n const customerUserId = auth?.sub ?? null\n const cache = buyerContextCache(container, { tenantId: store.tenantId, storeId: store.store.id })\n const cached = await cache.get(customerUserId)\n if (cached && cached.customerUserId === customerUserId) return cached\n const identity = customerUserId\n ? await loadBuyerIdentity(requireService<EntityManager>(container, 'em'), customerUserId, store)\n : ANONYMOUS_IDENTITY\n const buyer = await buildBuyerContext(container, store, identity)\n await cache.set(buyer)\n return buyer\n}\n\nexport function composeStoreContext(store: ResolvedStore, buyer: BuyerContext): StoreContext {\n return {\n store: store.store,\n tenantId: store.tenantId,\n organizationId: store.organizationId,\n channel: {\n channelBindingId: store.channel.channelBindingId,\n salesChannelId: store.channel.salesChannelId,\n priceKindId: store.channel.priceKindId,\n priceSortFallback: store.channel.priceSortFallback,\n },\n buyer,\n effectiveLocale: store.effectiveLocale,\n requestedLocale: store.requestedLocale,\n currencyCode: store.currencyCode,\n digest: computeStoreContextDigest({\n storeId: store.store.id,\n effectiveLocale: store.effectiveLocale,\n taxMode: buyer.taxMode,\n priceScopeKey: buyer.priceScopeKey,\n assortmentScopeHash: buyer.assortmentScopeHash,\n customerOverlayId: buyer.customerOverlayId,\n }),\n }\n}\n"],
|
|
5
|
+
"mappings": "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",
|
|
6
|
+
"names": []
|
|
7
|
+
}
|
|
@@ -0,0 +1,186 @@
|
|
|
1
|
+
import { createLogger } from "@open-mercato/shared/lib/logger";
|
|
2
|
+
import { getTelemetryRuntime } from "@open-mercato/shared/lib/telemetry/runtime";
|
|
3
|
+
import {
|
|
4
|
+
catalogCategoryTag,
|
|
5
|
+
catalogPriceTag,
|
|
6
|
+
catalogProductTag,
|
|
7
|
+
catalogProductsTag,
|
|
8
|
+
ecommerceCustomerGroupTag,
|
|
9
|
+
ecommerceCustomerTag,
|
|
10
|
+
ecommerceCustomerUserTag,
|
|
11
|
+
ecommerceDomainMappingTag,
|
|
12
|
+
ecommerceDomainTag,
|
|
13
|
+
ecommerceStoreTag,
|
|
14
|
+
ecommerceUngroupedBuyerTag,
|
|
15
|
+
invalidateEcommerceCacheTags,
|
|
16
|
+
storefrontAvailabilityTag
|
|
17
|
+
} from "./cacheKeys.js";
|
|
18
|
+
import {
|
|
19
|
+
asPayloadRecord,
|
|
20
|
+
eventAction,
|
|
21
|
+
readEventScope,
|
|
22
|
+
readPayloadString,
|
|
23
|
+
tryResolveService
|
|
24
|
+
} from "./subscriberSupport.js";
|
|
25
|
+
const logger = createLogger("ecommerce").child({ component: "cache-invalidation" });
|
|
26
|
+
const STORE_CACHE_ACTIONS = /* @__PURE__ */ new Set(["created", "updated", "deleted", "branding_updated"]);
|
|
27
|
+
const CUSTOMER_GROUP_CACHE_ACTIONS = /* @__PURE__ */ new Set(["created", "updated", "deleted"]);
|
|
28
|
+
const CUSTOMER_GROUP_DEFAULT_CANDIDATE_ACTIONS = /* @__PURE__ */ new Set(["created", "updated"]);
|
|
29
|
+
const CATALOG_CRUD_ACTIONS = /* @__PURE__ */ new Set(["created", "updated", "deleted"]);
|
|
30
|
+
const CUSTOMER_USER_CACHE_ACTIONS = /* @__PURE__ */ new Set(["updated", "deleted"]);
|
|
31
|
+
function tagsFrom(values, toTag) {
|
|
32
|
+
return values.filter((value) => value !== null).map(toTag);
|
|
33
|
+
}
|
|
34
|
+
const storeEventTags = (payload, action) => {
|
|
35
|
+
if (action && !STORE_CACHE_ACTIONS.has(action)) return [];
|
|
36
|
+
return tagsFrom([readPayloadString(payload, "id")], ecommerceStoreTag);
|
|
37
|
+
};
|
|
38
|
+
const storeDomainBindingEventTags = (payload) => [
|
|
39
|
+
...tagsFrom(
|
|
40
|
+
[readPayloadString(payload, "storeId"), readPayloadString(payload, "previousStoreId")],
|
|
41
|
+
ecommerceStoreTag
|
|
42
|
+
),
|
|
43
|
+
...tagsFrom(
|
|
44
|
+
[readPayloadString(payload, "domainMappingId"), readPayloadString(payload, "previousDomainMappingId")],
|
|
45
|
+
ecommerceDomainMappingTag
|
|
46
|
+
)
|
|
47
|
+
];
|
|
48
|
+
const storeChannelBindingEventTags = (payload) => tagsFrom([readPayloadString(payload, "storeId"), readPayloadString(payload, "previousStoreId")], ecommerceStoreTag);
|
|
49
|
+
const domainMappingEventTags = (payload) => [
|
|
50
|
+
...tagsFrom(
|
|
51
|
+
[readPayloadString(payload, "id"), readPayloadString(payload, "replacedDomainId")],
|
|
52
|
+
ecommerceDomainMappingTag
|
|
53
|
+
),
|
|
54
|
+
...tagsFrom(
|
|
55
|
+
[readPayloadString(payload, "hostname"), readPayloadString(payload, "replacedHostname")],
|
|
56
|
+
ecommerceDomainTag
|
|
57
|
+
)
|
|
58
|
+
];
|
|
59
|
+
const customerUserEventTags = (payload, action) => {
|
|
60
|
+
if (action && !CUSTOMER_USER_CACHE_ACTIONS.has(action)) return [];
|
|
61
|
+
return tagsFrom([readPayloadString(payload, "id")], ecommerceCustomerUserTag);
|
|
62
|
+
};
|
|
63
|
+
const customerGroupMembershipEventTags = (payload) => tagsFrom([readPayloadString(payload, "customerId")], ecommerceCustomerTag);
|
|
64
|
+
const customerGroupEventTags = (payload, action, tenantId) => {
|
|
65
|
+
if (action && !CUSTOMER_GROUP_CACHE_ACTIONS.has(action)) return [];
|
|
66
|
+
const tags = action === "created" ? [] : tagsFrom([readPayloadString(payload, "id")], ecommerceCustomerGroupTag);
|
|
67
|
+
if (tenantId && (!action || CUSTOMER_GROUP_DEFAULT_CANDIDATE_ACTIONS.has(action))) {
|
|
68
|
+
tags.push(ecommerceUngroupedBuyerTag(tenantId));
|
|
69
|
+
}
|
|
70
|
+
return tags;
|
|
71
|
+
};
|
|
72
|
+
const customerGroupTermsEventTags = (payload) => tagsFrom([readPayloadString(payload, "groupId")], ecommerceCustomerGroupTag);
|
|
73
|
+
async function invalidateForEvent(payload, ctx, deriveTags) {
|
|
74
|
+
const record = asPayloadRecord(payload);
|
|
75
|
+
const { tenantId } = readEventScope(record, ctx);
|
|
76
|
+
const tags = deriveTags(record, eventAction(ctx.eventName), tenantId);
|
|
77
|
+
if (tags.length === 0) return 0;
|
|
78
|
+
return invalidateEcommerceCacheTags(ctx, { tenantId, tags });
|
|
79
|
+
}
|
|
80
|
+
async function readPriceOwner(em, priceId, scope) {
|
|
81
|
+
let query = em.getKysely().selectFrom("catalog_product_variant_prices as price").leftJoin("catalog_product_variants as variant", "variant.id", "price.variant_id").select(["price.customer_id as customerId", "price.product_id as productId", "variant.product_id as variantProductId"]).where("price.id", "=", priceId).where("price.tenant_id", "=", scope.tenantId);
|
|
82
|
+
if (scope.organizationId) query = query.where("price.organization_id", "=", scope.organizationId);
|
|
83
|
+
const row = await query.executeTakeFirst();
|
|
84
|
+
if (!row) return null;
|
|
85
|
+
return { customerId: row.customerId ?? null, productId: row.productId ?? row.variantProductId ?? null };
|
|
86
|
+
}
|
|
87
|
+
async function readVariantProductId(em, variantId, scope) {
|
|
88
|
+
let query = em.getKysely().selectFrom("catalog_product_variants").select("product_id").where("id", "=", variantId).where("tenant_id", "=", scope.tenantId);
|
|
89
|
+
if (scope.organizationId) query = query.where("organization_id", "=", scope.organizationId);
|
|
90
|
+
const row = await query.executeTakeFirst();
|
|
91
|
+
return row?.product_id ?? null;
|
|
92
|
+
}
|
|
93
|
+
async function readPolicyProductId(em, policyId, scope) {
|
|
94
|
+
let query = em.getKysely().selectFrom("availability_policies").select("product_id").where("id", "=", policyId).where("tenant_id", "=", scope.tenantId);
|
|
95
|
+
if (scope.organizationId) query = query.where("organization_id", "=", scope.organizationId);
|
|
96
|
+
const row = await query.executeTakeFirst();
|
|
97
|
+
return row?.product_id ?? null;
|
|
98
|
+
}
|
|
99
|
+
async function lookupForEvent(ctx, id, scope, lookup, failureCode) {
|
|
100
|
+
const em = tryResolveService(ctx, "em");
|
|
101
|
+
if (!em) return void 0;
|
|
102
|
+
try {
|
|
103
|
+
return await lookup(em, id, scope) ?? void 0;
|
|
104
|
+
} catch (error) {
|
|
105
|
+
logger.warn("Storefront cache lookup failed; falling back to broader tags", { id, failureCode, err: error });
|
|
106
|
+
getTelemetryRuntime()?.reportError(error, {
|
|
107
|
+
module: "ecommerce",
|
|
108
|
+
code: failureCode,
|
|
109
|
+
attributes: { id }
|
|
110
|
+
});
|
|
111
|
+
return void 0;
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
function readScopedEvent(payload, ctx) {
|
|
115
|
+
const record = asPayloadRecord(payload);
|
|
116
|
+
const id = readPayloadString(record, "id");
|
|
117
|
+
const scope = readEventScope(record, ctx);
|
|
118
|
+
if (!id || !scope.tenantId) return null;
|
|
119
|
+
return { id, action: eventAction(ctx.eventName), scope: { tenantId: scope.tenantId, organizationId: scope.organizationId } };
|
|
120
|
+
}
|
|
121
|
+
function isCatalogCrudAction(action) {
|
|
122
|
+
return action === null || CATALOG_CRUD_ACTIONS.has(action);
|
|
123
|
+
}
|
|
124
|
+
async function invalidateForPriceEvent(payload, ctx) {
|
|
125
|
+
const event = readScopedEvent(payload, ctx);
|
|
126
|
+
if (!event || !isCatalogCrudAction(event.action)) return 0;
|
|
127
|
+
const owner = event.action === "deleted" ? void 0 : await lookupForEvent(ctx, event.id, event.scope, readPriceOwner, "ecommerce.price_cache_invalidation_failed");
|
|
128
|
+
const tags = [catalogProductsTag(event.scope.tenantId)];
|
|
129
|
+
if (owner?.productId) tags.push(catalogPriceTag(owner.productId));
|
|
130
|
+
if (owner?.customerId) tags.push(ecommerceCustomerTag(owner.customerId));
|
|
131
|
+
return invalidateEcommerceCacheTags(ctx, { tenantId: event.scope.tenantId, tags });
|
|
132
|
+
}
|
|
133
|
+
async function invalidateForProductEvent(payload, ctx) {
|
|
134
|
+
const event = readScopedEvent(payload, ctx);
|
|
135
|
+
if (!event || !isCatalogCrudAction(event.action)) return 0;
|
|
136
|
+
return invalidateEcommerceCacheTags(ctx, {
|
|
137
|
+
tenantId: event.scope.tenantId,
|
|
138
|
+
tags: [catalogProductTag(event.id), catalogProductsTag(event.scope.tenantId)]
|
|
139
|
+
});
|
|
140
|
+
}
|
|
141
|
+
async function invalidateForVariantEvent(payload, ctx) {
|
|
142
|
+
const event = readScopedEvent(payload, ctx);
|
|
143
|
+
if (!event || !isCatalogCrudAction(event.action)) return 0;
|
|
144
|
+
const productId = event.action === "deleted" ? void 0 : await lookupForEvent(ctx, event.id, event.scope, readVariantProductId, "ecommerce.variant_cache_invalidation_failed");
|
|
145
|
+
const tags = [catalogProductsTag(event.scope.tenantId)];
|
|
146
|
+
if (productId) tags.push(catalogProductTag(productId));
|
|
147
|
+
return invalidateEcommerceCacheTags(ctx, { tenantId: event.scope.tenantId, tags });
|
|
148
|
+
}
|
|
149
|
+
async function invalidateForCategoryEvent(payload, ctx) {
|
|
150
|
+
const event = readScopedEvent(payload, ctx);
|
|
151
|
+
if (!event || !isCatalogCrudAction(event.action)) return 0;
|
|
152
|
+
return invalidateEcommerceCacheTags(ctx, {
|
|
153
|
+
tenantId: event.scope.tenantId,
|
|
154
|
+
tags: [catalogCategoryTag(event.id), catalogProductsTag(event.scope.tenantId)]
|
|
155
|
+
});
|
|
156
|
+
}
|
|
157
|
+
async function invalidateForAvailabilityPolicyEvent(payload, ctx) {
|
|
158
|
+
const event = readScopedEvent(payload, ctx);
|
|
159
|
+
if (!event || !isCatalogCrudAction(event.action)) return 0;
|
|
160
|
+
const productId = await lookupForEvent(
|
|
161
|
+
ctx,
|
|
162
|
+
event.id,
|
|
163
|
+
event.scope,
|
|
164
|
+
readPolicyProductId,
|
|
165
|
+
"ecommerce.availability_policy_cache_invalidation_failed"
|
|
166
|
+
);
|
|
167
|
+
const tags = productId ? [catalogProductTag(productId), catalogProductsTag(event.scope.tenantId)] : [storefrontAvailabilityTag(event.scope.tenantId)];
|
|
168
|
+
return invalidateEcommerceCacheTags(ctx, { tenantId: event.scope.tenantId, tags });
|
|
169
|
+
}
|
|
170
|
+
export {
|
|
171
|
+
customerGroupEventTags,
|
|
172
|
+
customerGroupMembershipEventTags,
|
|
173
|
+
customerGroupTermsEventTags,
|
|
174
|
+
customerUserEventTags,
|
|
175
|
+
domainMappingEventTags,
|
|
176
|
+
invalidateForAvailabilityPolicyEvent,
|
|
177
|
+
invalidateForCategoryEvent,
|
|
178
|
+
invalidateForEvent,
|
|
179
|
+
invalidateForPriceEvent,
|
|
180
|
+
invalidateForProductEvent,
|
|
181
|
+
invalidateForVariantEvent,
|
|
182
|
+
storeChannelBindingEventTags,
|
|
183
|
+
storeDomainBindingEventTags,
|
|
184
|
+
storeEventTags
|
|
185
|
+
};
|
|
186
|
+
//# sourceMappingURL=cacheInvalidation.js.map
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": 3,
|
|
3
|
+
"sources": ["../../../../src/modules/ecommerce/lib/cacheInvalidation.ts"],
|
|
4
|
+
"sourcesContent": ["import type { EntityManager } from '@mikro-orm/postgresql'\nimport { createLogger } from '@open-mercato/shared/lib/logger'\nimport { getTelemetryRuntime } from '@open-mercato/shared/lib/telemetry/runtime'\nimport {\n catalogCategoryTag,\n catalogPriceTag,\n catalogProductTag,\n catalogProductsTag,\n ecommerceCustomerGroupTag,\n ecommerceCustomerTag,\n ecommerceCustomerUserTag,\n ecommerceDomainMappingTag,\n ecommerceDomainTag,\n ecommerceStoreTag,\n ecommerceUngroupedBuyerTag,\n invalidateEcommerceCacheTags,\n storefrontAvailabilityTag,\n} from './cacheKeys'\nimport {\n asPayloadRecord,\n eventAction,\n readEventScope,\n readPayloadString,\n tryResolveService,\n type EcommerceSubscriberContext,\n} from './subscriberSupport'\n\n/**\n * Event \u2192 cache-tag mapping for the storefront caches (SPEC-029 \u00A78 table). Every eviction goes\n * through `invalidateEcommerceCacheTags`, which clears the tenant-agnostic resolution cache and the\n * emitting tenant's buyer/storefront caches.\n */\n\nconst logger = createLogger('ecommerce').child({ component: 'cache-invalidation' })\n\ntype TagDeriver = (payload: Record<string, unknown>, action: string | null, tenantId: string | null) => string[]\n\nconst STORE_CACHE_ACTIONS: ReadonlySet<string> = new Set(['created', 'updated', 'deleted', 'branding_updated'])\nconst CUSTOMER_GROUP_CACHE_ACTIONS: ReadonlySet<string> = new Set(['created', 'updated', 'deleted'])\nconst CUSTOMER_GROUP_DEFAULT_CANDIDATE_ACTIONS: ReadonlySet<string> = new Set(['created', 'updated'])\nconst CATALOG_CRUD_ACTIONS: ReadonlySet<string> = new Set(['created', 'updated', 'deleted'])\nconst CUSTOMER_USER_CACHE_ACTIONS: ReadonlySet<string> = new Set(['updated', 'deleted'])\n\nfunction tagsFrom(values: Array<string | null>, toTag: (value: string) => string): string[] {\n return values.filter((value): value is string => value !== null).map(toTag)\n}\n\nexport const storeEventTags: TagDeriver = (payload, action) => {\n if (action && !STORE_CACHE_ACTIONS.has(action)) return []\n return tagsFrom([readPayloadString(payload, 'id')], ecommerceStoreTag)\n}\n\nexport const storeDomainBindingEventTags: TagDeriver = (payload) => [\n ...tagsFrom(\n [readPayloadString(payload, 'storeId'), readPayloadString(payload, 'previousStoreId')],\n ecommerceStoreTag,\n ),\n ...tagsFrom(\n [readPayloadString(payload, 'domainMappingId'), readPayloadString(payload, 'previousDomainMappingId')],\n ecommerceDomainMappingTag,\n ),\n]\n\nexport const storeChannelBindingEventTags: TagDeriver = (payload) =>\n tagsFrom([readPayloadString(payload, 'storeId'), readPayloadString(payload, 'previousStoreId')], ecommerceStoreTag)\n\nexport const domainMappingEventTags: TagDeriver = (payload) => [\n ...tagsFrom(\n [readPayloadString(payload, 'id'), readPayloadString(payload, 'replacedDomainId')],\n ecommerceDomainMappingTag,\n ),\n ...tagsFrom(\n [readPayloadString(payload, 'hostname'), readPayloadString(payload, 'replacedHostname')],\n ecommerceDomainTag,\n ),\n]\n\n/**\n * `customer_accounts.user.updated|deleted`: the user's buyer contexts, so a deactivated or deleted\n * portal user stops resolving to their cached identity (and contract prices) before the TTL ends.\n */\nexport const customerUserEventTags: TagDeriver = (payload, action) => {\n if (action && !CUSTOMER_USER_CACHE_ACTIONS.has(action)) return []\n return tagsFrom([readPayloadString(payload, 'id')], ecommerceCustomerUserTag)\n}\n\nexport const customerGroupMembershipEventTags: TagDeriver = (payload) =>\n tagsFrom([readPayloadString(payload, 'customerId')], ecommerceCustomerTag)\n\n/**\n * `customer_groups.group.*`: the group's buyer contexts. The payload carries no `isDefault`, so a\n * create or update also evicts the tenant's ungrouped buyer contexts, which would fall back to the\n * group if it became the tenant default; a replaced default is announced as its own update.\n */\nexport const customerGroupEventTags: TagDeriver = (payload, action, tenantId) => {\n if (action && !CUSTOMER_GROUP_CACHE_ACTIONS.has(action)) return []\n const tags = action === 'created' ? [] : tagsFrom([readPayloadString(payload, 'id')], ecommerceCustomerGroupTag)\n if (tenantId && (!action || CUSTOMER_GROUP_DEFAULT_CANDIDATE_ACTIONS.has(action))) {\n tags.push(ecommerceUngroupedBuyerTag(tenantId))\n }\n return tags\n}\n\nexport const customerGroupTermsEventTags: TagDeriver = (payload) =>\n tagsFrom([readPayloadString(payload, 'groupId')], ecommerceCustomerGroupTag)\n\nexport async function invalidateForEvent(\n payload: unknown,\n ctx: EcommerceSubscriberContext,\n deriveTags: TagDeriver,\n): Promise<number> {\n const record = asPayloadRecord(payload)\n const { tenantId } = readEventScope(record, ctx)\n const tags = deriveTags(record, eventAction(ctx.eventName), tenantId)\n if (tags.length === 0) return 0\n return invalidateEcommerceCacheTags(ctx, { tenantId, tags })\n}\n\ntype CatalogLookupDatabase = {\n catalog_product_variant_prices: {\n id: string\n tenant_id: string\n organization_id: string\n customer_id: string | null\n product_id: string | null\n variant_id: string | null\n }\n catalog_product_variants: {\n id: string\n tenant_id: string\n organization_id: string\n product_id: string\n }\n availability_policies: {\n id: string\n tenant_id: string\n organization_id: string\n product_id: string | null\n }\n}\n\ntype PriceOwner = { customerId: string | null; productId: string | null }\n\ntype LookupScope = { tenantId: string; organizationId: string | null }\n\nasync function readPriceOwner(em: EntityManager, priceId: string, scope: LookupScope): Promise<PriceOwner | null> {\n let query = em\n .getKysely<CatalogLookupDatabase>()\n .selectFrom('catalog_product_variant_prices as price')\n .leftJoin('catalog_product_variants as variant', 'variant.id', 'price.variant_id')\n .select(['price.customer_id as customerId', 'price.product_id as productId', 'variant.product_id as variantProductId'])\n .where('price.id', '=', priceId)\n .where('price.tenant_id', '=', scope.tenantId)\n if (scope.organizationId) query = query.where('price.organization_id', '=', scope.organizationId)\n const row = await query.executeTakeFirst()\n if (!row) return null\n return { customerId: row.customerId ?? null, productId: row.productId ?? row.variantProductId ?? null }\n}\n\nasync function readVariantProductId(em: EntityManager, variantId: string, scope: LookupScope): Promise<string | null> {\n let query = em\n .getKysely<CatalogLookupDatabase>()\n .selectFrom('catalog_product_variants')\n .select('product_id')\n .where('id', '=', variantId)\n .where('tenant_id', '=', scope.tenantId)\n if (scope.organizationId) query = query.where('organization_id', '=', scope.organizationId)\n const row = await query.executeTakeFirst()\n return row?.product_id ?? null\n}\n\nasync function readPolicyProductId(em: EntityManager, policyId: string, scope: LookupScope): Promise<string | null> {\n let query = em\n .getKysely<CatalogLookupDatabase>()\n .selectFrom('availability_policies')\n .select('product_id')\n .where('id', '=', policyId)\n .where('tenant_id', '=', scope.tenantId)\n if (scope.organizationId) query = query.where('organization_id', '=', scope.organizationId)\n const row = await query.executeTakeFirst()\n return row?.product_id ?? null\n}\n\n/**\n * Reads the row an id-only catalog/availability payload points at. A missing `em`, a missing row\n * and a failed lookup all answer `undefined`, so the caller falls back to its broader tag.\n */\nasync function lookupForEvent<T>(\n ctx: EcommerceSubscriberContext,\n id: string,\n scope: LookupScope,\n lookup: (em: EntityManager, id: string, scope: LookupScope) => Promise<T | null>,\n failureCode: string,\n): Promise<T | undefined> {\n const em = tryResolveService<EntityManager>(ctx, 'em')\n if (!em) return undefined\n try {\n return (await lookup(em, id, scope)) ?? undefined\n } catch (error) {\n logger.warn('Storefront cache lookup failed; falling back to broader tags', { id, failureCode, err: error })\n getTelemetryRuntime()?.reportError(error, {\n module: 'ecommerce',\n code: failureCode,\n attributes: { id },\n })\n return undefined\n }\n}\n\ntype ScopedEvent = { id: string; action: string | null; scope: LookupScope }\n\nfunction readScopedEvent(payload: unknown, ctx: EcommerceSubscriberContext): ScopedEvent | null {\n const record = asPayloadRecord(payload)\n const id = readPayloadString(record, 'id')\n const scope = readEventScope(record, ctx)\n if (!id || !scope.tenantId) return null\n return { id, action: eventAction(ctx.eventName), scope: { tenantId: scope.tenantId, organizationId: scope.organizationId } }\n}\n\nfunction isCatalogCrudAction(action: string | null): boolean {\n return action === null || CATALOG_CRUD_ACTIONS.has(action)\n}\n\n/**\n * `catalog.price.*` (\u00A78 buyer context, Storefront Public API \u00A79): evicts the product's\n * `catalog-price` tag and every listing of the tenant, plus the owning customer's buyer context\n * when the row is customer-specific (`customerOverlayId`). The payload carries ids only, so the row\n * is read back; a deleted row is gone, so a delete evicts the listings only and the product detail\n * and buyer context fall back to their TTLs (catalog declares no price-kind events either).\n */\nexport async function invalidateForPriceEvent(payload: unknown, ctx: EcommerceSubscriberContext): Promise<number> {\n const event = readScopedEvent(payload, ctx)\n if (!event || !isCatalogCrudAction(event.action)) return 0\n const owner =\n event.action === 'deleted'\n ? undefined\n : await lookupForEvent(ctx, event.id, event.scope, readPriceOwner, 'ecommerce.price_cache_invalidation_failed')\n const tags = [catalogProductsTag(event.scope.tenantId)]\n if (owner?.productId) tags.push(catalogPriceTag(owner.productId))\n if (owner?.customerId) tags.push(ecommerceCustomerTag(owner.customerId))\n return invalidateEcommerceCacheTags(ctx, { tenantId: event.scope.tenantId, tags })\n}\n\n/** `catalog.product.created|updated|deleted`: the product's detail entries and every listing of the tenant. */\nexport async function invalidateForProductEvent(payload: unknown, ctx: EcommerceSubscriberContext): Promise<number> {\n const event = readScopedEvent(payload, ctx)\n if (!event || !isCatalogCrudAction(event.action)) return 0\n return invalidateEcommerceCacheTags(ctx, {\n tenantId: event.scope.tenantId,\n tags: [catalogProductTag(event.id), catalogProductsTag(event.scope.tenantId)],\n })\n}\n\n/**\n * `catalog.variant.*`: the variant's product (read back \u2014 the payload carries ids only) and every\n * listing of the tenant. A deleted variant row is gone, so its product detail falls back to its TTL.\n */\nexport async function invalidateForVariantEvent(payload: unknown, ctx: EcommerceSubscriberContext): Promise<number> {\n const event = readScopedEvent(payload, ctx)\n if (!event || !isCatalogCrudAction(event.action)) return 0\n const productId =\n event.action === 'deleted'\n ? undefined\n : await lookupForEvent(ctx, event.id, event.scope, readVariantProductId, 'ecommerce.variant_cache_invalidation_failed')\n const tags = [catalogProductsTag(event.scope.tenantId)]\n if (productId) tags.push(catalogProductTag(productId))\n return invalidateEcommerceCacheTags(ctx, { tenantId: event.scope.tenantId, tags })\n}\n\n/** `catalog.category.*`: details showing the category and every listing (category names, filters, assortment). */\nexport async function invalidateForCategoryEvent(payload: unknown, ctx: EcommerceSubscriberContext): Promise<number> {\n const event = readScopedEvent(payload, ctx)\n if (!event || !isCatalogCrudAction(event.action)) return 0\n return invalidateEcommerceCacheTags(ctx, {\n tenantId: event.scope.tenantId,\n tags: [catalogCategoryTag(event.id), catalogProductsTag(event.scope.tenantId)],\n })\n}\n\n/**\n * `availability.policy.*`: a policy tied to a product evicts that product and every listing; a\n * store- or tenant-wide policy (or one that cannot be read back) evicts every storefront product\n * entry of the tenant. Policies are soft-deleted, so a delete still reads back its product.\n */\nexport async function invalidateForAvailabilityPolicyEvent(\n payload: unknown,\n ctx: EcommerceSubscriberContext,\n): Promise<number> {\n const event = readScopedEvent(payload, ctx)\n if (!event || !isCatalogCrudAction(event.action)) return 0\n const productId = await lookupForEvent(\n ctx,\n event.id,\n event.scope,\n readPolicyProductId,\n 'ecommerce.availability_policy_cache_invalidation_failed',\n )\n const tags = productId\n ? [catalogProductTag(productId), catalogProductsTag(event.scope.tenantId)]\n : [storefrontAvailabilityTag(event.scope.tenantId)]\n return invalidateEcommerceCacheTags(ctx, { tenantId: event.scope.tenantId, tags })\n}\n"],
|
|
5
|
+
"mappings": "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",
|
|
6
|
+
"names": []
|
|
7
|
+
}
|
|
@@ -0,0 +1,236 @@
|
|
|
1
|
+
import { createHash } from "node:crypto";
|
|
2
|
+
import { runWithCacheTenant } from "@open-mercato/cache";
|
|
3
|
+
import { createLogger } from "@open-mercato/shared/lib/logger";
|
|
4
|
+
import { getTelemetryRuntime } from "@open-mercato/shared/lib/telemetry/runtime";
|
|
5
|
+
const BUYER_CONTEXT_TTL_MS = 6e4;
|
|
6
|
+
const STOREFRONT_KEY_NAMESPACE = "ecommerce:storefront";
|
|
7
|
+
const RESOLUTION_KEY_NAMESPACE = "ecommerce:resolution";
|
|
8
|
+
const BUYER_KEY_NAMESPACE = "ecommerce:buyer";
|
|
9
|
+
const ANONYMOUS_SEGMENT = "anonymous";
|
|
10
|
+
const NULL_SEGMENT = "-";
|
|
11
|
+
const SCOPE_SEGMENTS = {
|
|
12
|
+
digest: "digest",
|
|
13
|
+
assortment: "assortment",
|
|
14
|
+
priceScope: "price-scope",
|
|
15
|
+
store: "store"
|
|
16
|
+
};
|
|
17
|
+
const logger = createLogger("ecommerce").child({ component: "storefront-cache" });
|
|
18
|
+
function encodeSegment(value) {
|
|
19
|
+
return encodeURIComponent(value);
|
|
20
|
+
}
|
|
21
|
+
function encodeParts(parts) {
|
|
22
|
+
if (parts.length === 0) {
|
|
23
|
+
throw new Error("[internal] ecommerce cache keys require at least one part");
|
|
24
|
+
}
|
|
25
|
+
return parts.map(encodeSegment).join(":");
|
|
26
|
+
}
|
|
27
|
+
function scopeSegments(ctx, scope) {
|
|
28
|
+
const storeId = ctx.store.id;
|
|
29
|
+
switch (scope) {
|
|
30
|
+
case "digest":
|
|
31
|
+
return [storeId, ctx.digest];
|
|
32
|
+
case "assortment":
|
|
33
|
+
return [storeId, ctx.effectiveLocale, ctx.buyer.assortmentScopeHash];
|
|
34
|
+
case "priceScope":
|
|
35
|
+
return [storeId, ctx.buyer.priceScopeKey, ctx.buyer.customerOverlayId ?? NULL_SEGMENT];
|
|
36
|
+
case "store":
|
|
37
|
+
return [storeId, ctx.effectiveLocale];
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
function buildStorefrontCacheKey(ctx, parts, options) {
|
|
41
|
+
const scope = options?.scope ?? "digest";
|
|
42
|
+
const prefix = [SCOPE_SEGMENTS[scope], ...scopeSegments(ctx, scope).map(encodeSegment)].join(":");
|
|
43
|
+
return `${STOREFRONT_KEY_NAMESPACE}:${prefix}:${encodeParts(parts)}`;
|
|
44
|
+
}
|
|
45
|
+
function canonicalCacheValue(value) {
|
|
46
|
+
if (Array.isArray(value)) return value.map(canonicalCacheValue);
|
|
47
|
+
if (value && typeof value === "object") {
|
|
48
|
+
return Object.fromEntries(
|
|
49
|
+
Object.entries(value).filter((entry) => entry[1] !== void 0).sort(([left], [right]) => left < right ? -1 : left > right ? 1 : 0).map(([key, entry]) => [key, canonicalCacheValue(entry)])
|
|
50
|
+
);
|
|
51
|
+
}
|
|
52
|
+
return value;
|
|
53
|
+
}
|
|
54
|
+
function storefrontCacheValueHash(value) {
|
|
55
|
+
return createHash("sha256").update(JSON.stringify(canonicalCacheValue(value))).digest("hex");
|
|
56
|
+
}
|
|
57
|
+
function buildEcommerceResolutionCacheKey(parts) {
|
|
58
|
+
return `${RESOLUTION_KEY_NAMESPACE}:${encodeParts(parts)}`;
|
|
59
|
+
}
|
|
60
|
+
function buildBuyerContextCacheKey(storeId, customerUserId) {
|
|
61
|
+
return `${BUYER_KEY_NAMESPACE}:${encodeParts([storeId, customerUserId ?? ANONYMOUS_SEGMENT])}`;
|
|
62
|
+
}
|
|
63
|
+
function ecommerceStoreTag(storeId) {
|
|
64
|
+
return `ecommerce-store:${storeId}`;
|
|
65
|
+
}
|
|
66
|
+
function ecommerceDomainMappingTag(domainMappingId) {
|
|
67
|
+
return `ecommerce-domain-mapping:${domainMappingId}`;
|
|
68
|
+
}
|
|
69
|
+
function ecommerceDomainTag(hostname) {
|
|
70
|
+
return `ecommerce-domain:${hostname}`;
|
|
71
|
+
}
|
|
72
|
+
function ecommerceCustomerTag(customerId) {
|
|
73
|
+
return `customer:${customerId}`;
|
|
74
|
+
}
|
|
75
|
+
function ecommerceCustomerUserTag(customerUserId) {
|
|
76
|
+
return `customer-user:${customerUserId}`;
|
|
77
|
+
}
|
|
78
|
+
function ecommerceCustomerGroupTag(customerGroupId) {
|
|
79
|
+
return `customer-group:${customerGroupId}`;
|
|
80
|
+
}
|
|
81
|
+
function catalogProductTag(productId) {
|
|
82
|
+
return `catalog-product:${productId}`;
|
|
83
|
+
}
|
|
84
|
+
function catalogProductsTag(tenantId) {
|
|
85
|
+
return `catalog-products:${tenantId}`;
|
|
86
|
+
}
|
|
87
|
+
function catalogCategoryTag(categoryId) {
|
|
88
|
+
return `catalog-category:${categoryId}`;
|
|
89
|
+
}
|
|
90
|
+
function catalogPriceTag(productId) {
|
|
91
|
+
return `catalog-price:${productId}`;
|
|
92
|
+
}
|
|
93
|
+
function storefrontAvailabilityTag(tenantId) {
|
|
94
|
+
return `availability:${tenantId}`;
|
|
95
|
+
}
|
|
96
|
+
function ecommerceUngroupedBuyerTag(tenantId) {
|
|
97
|
+
return `customer-group-none:${tenantId}`;
|
|
98
|
+
}
|
|
99
|
+
function buyerContextTags(buyer, tenantId) {
|
|
100
|
+
return [
|
|
101
|
+
...buyer.customerUserId ? [ecommerceCustomerUserTag(buyer.customerUserId)] : [],
|
|
102
|
+
...buyer.customerIds.map(ecommerceCustomerTag),
|
|
103
|
+
...buyer.customerGroupIds.map(ecommerceCustomerGroupTag),
|
|
104
|
+
...tenantId && buyer.customerGroupIds.length === 0 ? [ecommerceUngroupedBuyerTag(tenantId)] : []
|
|
105
|
+
];
|
|
106
|
+
}
|
|
107
|
+
function resolveCacheService(container) {
|
|
108
|
+
if (!container?.resolve) return null;
|
|
109
|
+
try {
|
|
110
|
+
const candidate = container.resolve("cache");
|
|
111
|
+
if (candidate && typeof candidate.get === "function" && typeof candidate.set === "function" && typeof candidate.deleteByTags === "function") {
|
|
112
|
+
return candidate;
|
|
113
|
+
}
|
|
114
|
+
} catch {
|
|
115
|
+
return null;
|
|
116
|
+
}
|
|
117
|
+
return null;
|
|
118
|
+
}
|
|
119
|
+
function reportCacheFailure(operation, error) {
|
|
120
|
+
logger.warn("Ecommerce cache operation failed; degrading to a cache miss", { operation, err: error });
|
|
121
|
+
getTelemetryRuntime()?.reportError(error, {
|
|
122
|
+
module: "ecommerce",
|
|
123
|
+
code: "ecommerce.cache_operation_failed",
|
|
124
|
+
attributes: { operation }
|
|
125
|
+
});
|
|
126
|
+
}
|
|
127
|
+
function createTenantScopedAccess({ cache, tenantId, baseTags }) {
|
|
128
|
+
return {
|
|
129
|
+
async get(key) {
|
|
130
|
+
if (!cache) return null;
|
|
131
|
+
try {
|
|
132
|
+
const value = await runWithCacheTenant(tenantId, () => cache.get(key));
|
|
133
|
+
return value ?? null;
|
|
134
|
+
} catch (error) {
|
|
135
|
+
reportCacheFailure("get", error);
|
|
136
|
+
return null;
|
|
137
|
+
}
|
|
138
|
+
},
|
|
139
|
+
async set(key, value, ttlMs, tags) {
|
|
140
|
+
if (!cache) return;
|
|
141
|
+
const mergedTags = Array.from(/* @__PURE__ */ new Set([...baseTags, ...tags ?? []]));
|
|
142
|
+
try {
|
|
143
|
+
await runWithCacheTenant(tenantId, () => cache.set(key, value, { ttl: ttlMs, tags: mergedTags }));
|
|
144
|
+
} catch (error) {
|
|
145
|
+
reportCacheFailure("set", error);
|
|
146
|
+
}
|
|
147
|
+
},
|
|
148
|
+
async deleteByTags(tags) {
|
|
149
|
+
if (!cache || tags.length === 0) return 0;
|
|
150
|
+
try {
|
|
151
|
+
return await runWithCacheTenant(tenantId, () => cache.deleteByTags(tags));
|
|
152
|
+
} catch (error) {
|
|
153
|
+
reportCacheFailure("deleteByTags", error);
|
|
154
|
+
return 0;
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
};
|
|
158
|
+
}
|
|
159
|
+
function storefrontCache(container, ctx) {
|
|
160
|
+
const access = createTenantScopedAccess({
|
|
161
|
+
cache: resolveCacheService(container),
|
|
162
|
+
tenantId: ctx.tenantId,
|
|
163
|
+
baseTags: [ecommerceStoreTag(ctx.store.id)]
|
|
164
|
+
});
|
|
165
|
+
return {
|
|
166
|
+
get: (parts, options) => access.get(buildStorefrontCacheKey(ctx, parts, options)),
|
|
167
|
+
set: (parts, value, options) => access.set(buildStorefrontCacheKey(ctx, parts, { scope: options.scope }), value, options.ttlMs, options.tags),
|
|
168
|
+
deleteByTags: (tags) => access.deleteByTags(tags)
|
|
169
|
+
};
|
|
170
|
+
}
|
|
171
|
+
function ecommerceResolutionCache(container) {
|
|
172
|
+
const access = createTenantScopedAccess({
|
|
173
|
+
cache: resolveCacheService(container),
|
|
174
|
+
tenantId: null,
|
|
175
|
+
baseTags: []
|
|
176
|
+
});
|
|
177
|
+
return {
|
|
178
|
+
get: (parts) => access.get(buildEcommerceResolutionCacheKey(parts)),
|
|
179
|
+
set: (parts, value, options) => access.set(buildEcommerceResolutionCacheKey(parts), value, options.ttlMs, options.tags),
|
|
180
|
+
deleteByTags: (tags) => access.deleteByTags(tags)
|
|
181
|
+
};
|
|
182
|
+
}
|
|
183
|
+
async function invalidateEcommerceCacheTags(container, invalidation) {
|
|
184
|
+
const tags = Array.from(new Set(invalidation.tags.filter((tag) => tag.length > 0)));
|
|
185
|
+
if (tags.length === 0) return 0;
|
|
186
|
+
const cache = resolveCacheService(container);
|
|
187
|
+
if (!cache) return 0;
|
|
188
|
+
const tenantIds = invalidation.tenantId ? [null, invalidation.tenantId] : [null];
|
|
189
|
+
let deleted = 0;
|
|
190
|
+
for (const tenantId of tenantIds) {
|
|
191
|
+
deleted += await createTenantScopedAccess({ cache, tenantId, baseTags: [] }).deleteByTags(tags);
|
|
192
|
+
}
|
|
193
|
+
return deleted;
|
|
194
|
+
}
|
|
195
|
+
function buyerContextCache(container, scope) {
|
|
196
|
+
const access = createTenantScopedAccess({
|
|
197
|
+
cache: resolveCacheService(container),
|
|
198
|
+
tenantId: scope.tenantId,
|
|
199
|
+
baseTags: [ecommerceStoreTag(scope.storeId)]
|
|
200
|
+
});
|
|
201
|
+
return {
|
|
202
|
+
get: (customerUserId) => access.get(buildBuyerContextCacheKey(scope.storeId, customerUserId)),
|
|
203
|
+
set: (buyer) => access.set(
|
|
204
|
+
buildBuyerContextCacheKey(scope.storeId, buyer.customerUserId),
|
|
205
|
+
buyer,
|
|
206
|
+
BUYER_CONTEXT_TTL_MS,
|
|
207
|
+
buyerContextTags(buyer, scope.tenantId)
|
|
208
|
+
),
|
|
209
|
+
deleteByTags: (tags) => access.deleteByTags(tags)
|
|
210
|
+
};
|
|
211
|
+
}
|
|
212
|
+
export {
|
|
213
|
+
BUYER_CONTEXT_TTL_MS,
|
|
214
|
+
buildBuyerContextCacheKey,
|
|
215
|
+
buildEcommerceResolutionCacheKey,
|
|
216
|
+
buildStorefrontCacheKey,
|
|
217
|
+
buyerContextCache,
|
|
218
|
+
buyerContextTags,
|
|
219
|
+
catalogCategoryTag,
|
|
220
|
+
catalogPriceTag,
|
|
221
|
+
catalogProductTag,
|
|
222
|
+
catalogProductsTag,
|
|
223
|
+
ecommerceCustomerGroupTag,
|
|
224
|
+
ecommerceCustomerTag,
|
|
225
|
+
ecommerceCustomerUserTag,
|
|
226
|
+
ecommerceDomainMappingTag,
|
|
227
|
+
ecommerceDomainTag,
|
|
228
|
+
ecommerceResolutionCache,
|
|
229
|
+
ecommerceStoreTag,
|
|
230
|
+
ecommerceUngroupedBuyerTag,
|
|
231
|
+
invalidateEcommerceCacheTags,
|
|
232
|
+
storefrontAvailabilityTag,
|
|
233
|
+
storefrontCache,
|
|
234
|
+
storefrontCacheValueHash
|
|
235
|
+
};
|
|
236
|
+
//# sourceMappingURL=cacheKeys.js.map
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": 3,
|
|
3
|
+
"sources": ["../../../../src/modules/ecommerce/lib/cacheKeys.ts"],
|
|
4
|
+
"sourcesContent": ["import { createHash } from 'node:crypto'\nimport { runWithCacheTenant, type CacheStrategy } from '@open-mercato/cache'\nimport { createLogger } from '@open-mercato/shared/lib/logger'\nimport { getTelemetryRuntime } from '@open-mercato/shared/lib/telemetry/runtime'\nimport type { BuyerContext, StoreContext } from './types'\n\n/**\n * The ONLY file in the ecommerce module allowed to resolve the DI `cache` (SPEC-029 \u00A76.1\n * \"Enforcement\", D7). Every storefront key is built by `buildStorefrontCacheKey`; callers pass\n * `parts`, never a raw key. `__tests__/no-raw-cache-access.test.ts` fails on any other access.\n */\n\nexport type StorefrontCacheScope = 'digest' | 'assortment' | 'priceScope' | 'store'\n\nexport type StorefrontCacheKeyOptions = { scope?: StorefrontCacheScope }\n\nexport type StorefrontCacheSetOptions = {\n ttlMs: number\n tags?: string[]\n scope?: StorefrontCacheScope\n}\n\nexport type StorefrontCache = {\n get<T>(parts: string[], options?: StorefrontCacheKeyOptions): Promise<T | null>\n set<T>(parts: string[], value: T, options: StorefrontCacheSetOptions): Promise<void>\n deleteByTags(tags: string[]): Promise<number>\n}\n\nexport type EcommerceResolutionCacheSetOptions = { ttlMs: number; tags?: string[] }\n\n/**\n * Store-resolution cache (host/slug \u2192 store + default channel binding, \u00A78.1 steps 3\u20135). It runs\n * BEFORE a `StoreContext` exists, so it is tenant-agnostic and MUST NEVER carry buyer data.\n */\nexport type EcommerceResolutionCache = {\n get<T>(parts: string[]): Promise<T | null>\n set<T>(parts: string[], value: T, options: EcommerceResolutionCacheSetOptions): Promise<void>\n deleteByTags(tags: string[]): Promise<number>\n}\n\n/**\n * Buyer-context cache (\u00A78, step 6): one entry per (store, customerUserId | anonymous), holding the\n * locale-independent buyer layer only. Tagged per store, per portal user, per identity in\n * `customerIds` and per contributing group so user, membership, terms, group and price events can\n * evict it.\n */\nexport type BuyerContextCache = {\n get(customerUserId: string | null): Promise<BuyerContext | null>\n set(buyer: BuyerContext): Promise<void>\n deleteByTags(tags: string[]): Promise<number>\n}\n\nexport type BuyerContextCacheScope = { tenantId: string; storeId: string }\n\nexport type CacheContainer = { resolve: (name: string) => unknown }\n\nexport const BUYER_CONTEXT_TTL_MS = 60_000\n\nconst STOREFRONT_KEY_NAMESPACE = 'ecommerce:storefront'\nconst RESOLUTION_KEY_NAMESPACE = 'ecommerce:resolution'\nconst BUYER_KEY_NAMESPACE = 'ecommerce:buyer'\nconst ANONYMOUS_SEGMENT = 'anonymous'\nconst NULL_SEGMENT = '-'\n\nconst SCOPE_SEGMENTS: Record<StorefrontCacheScope, string> = {\n digest: 'digest',\n assortment: 'assortment',\n priceScope: 'price-scope',\n store: 'store',\n}\n\nconst logger = createLogger('ecommerce').child({ component: 'storefront-cache' })\n\nfunction encodeSegment(value: string): string {\n return encodeURIComponent(value)\n}\n\nfunction encodeParts(parts: string[]): string {\n if (parts.length === 0) {\n throw new Error('[internal] ecommerce cache keys require at least one part')\n }\n return parts.map(encodeSegment).join(':')\n}\n\nfunction scopeSegments(ctx: StoreContext, scope: StorefrontCacheScope): string[] {\n const storeId = ctx.store.id\n switch (scope) {\n case 'digest':\n return [storeId, ctx.digest]\n case 'assortment':\n return [storeId, ctx.effectiveLocale, ctx.buyer.assortmentScopeHash]\n case 'priceScope':\n return [storeId, ctx.buyer.priceScopeKey, ctx.buyer.customerOverlayId ?? NULL_SEGMENT]\n case 'store':\n return [storeId, ctx.effectiveLocale]\n }\n}\n\nexport function buildStorefrontCacheKey(\n ctx: StoreContext,\n parts: string[],\n options?: StorefrontCacheKeyOptions,\n): string {\n const scope = options?.scope ?? 'digest'\n const prefix = [SCOPE_SEGMENTS[scope], ...scopeSegments(ctx, scope).map(encodeSegment)].join(':')\n return `${STOREFRONT_KEY_NAMESPACE}:${prefix}:${encodeParts(parts)}`\n}\n\nfunction canonicalCacheValue(value: unknown): unknown {\n if (Array.isArray(value)) return value.map(canonicalCacheValue)\n if (value && typeof value === 'object') {\n return Object.fromEntries(\n Object.entries(value as Record<string, unknown>)\n .filter((entry) => entry[1] !== undefined)\n .sort(([left], [right]) => (left < right ? -1 : left > right ? 1 : 0))\n .map(([key, entry]) => [key, canonicalCacheValue(entry)]),\n )\n }\n return value\n}\n\n/** A fixed-length key part for a structured value: object keys sorted, `undefined` entries dropped, sha256-hashed. */\nexport function storefrontCacheValueHash(value: unknown): string {\n return createHash('sha256').update(JSON.stringify(canonicalCacheValue(value))).digest('hex')\n}\n\nexport function buildEcommerceResolutionCacheKey(parts: string[]): string {\n return `${RESOLUTION_KEY_NAMESPACE}:${encodeParts(parts)}`\n}\n\nexport function buildBuyerContextCacheKey(storeId: string, customerUserId: string | null): string {\n return `${BUYER_KEY_NAMESPACE}:${encodeParts([storeId, customerUserId ?? ANONYMOUS_SEGMENT])}`\n}\n\nexport function ecommerceStoreTag(storeId: string): string {\n return `ecommerce-store:${storeId}`\n}\n\nexport function ecommerceDomainMappingTag(domainMappingId: string): string {\n return `ecommerce-domain-mapping:${domainMappingId}`\n}\n\nexport function ecommerceDomainTag(hostname: string): string {\n return `ecommerce-domain:${hostname}`\n}\n\nexport function ecommerceCustomerTag(customerId: string): string {\n return `customer:${customerId}`\n}\n\n/** One portal user's buyer contexts: evicted when the user is updated (e.g. deactivated) or deleted. */\nexport function ecommerceCustomerUserTag(customerUserId: string): string {\n return `customer-user:${customerUserId}`\n}\n\nexport function ecommerceCustomerGroupTag(customerGroupId: string): string {\n return `customer-group:${customerGroupId}`\n}\n\nexport function catalogProductTag(productId: string): string {\n return `catalog-product:${productId}`\n}\n\n/** Every storefront listing of a tenant: evicted by any product, variant, category or price change. */\nexport function catalogProductsTag(tenantId: string): string {\n return `catalog-products:${tenantId}`\n}\n\nexport function catalogCategoryTag(categoryId: string): string {\n return `catalog-category:${categoryId}`\n}\n\nexport function catalogPriceTag(productId: string): string {\n return `catalog-price:${productId}`\n}\n\n/** Every storefront product entry of a tenant: evicted by an availability policy not tied to one product. */\nexport function storefrontAvailabilityTag(tenantId: string): string {\n return `availability:${tenantId}`\n}\n\n/**\n * Buyer contexts of a tenant that resolved to no customer group (anonymous or unassigned buyers\n * while the tenant has no default group): evicted when a group is created or updated, since that\n * group may have become the default they fall back to.\n */\nexport function ecommerceUngroupedBuyerTag(tenantId: string): string {\n return `customer-group-none:${tenantId}`\n}\n\nexport function buyerContextTags(\n buyer: Pick<BuyerContext, 'customerIds' | 'customerGroupIds'> & { customerUserId?: string | null },\n tenantId?: string,\n): string[] {\n return [\n ...(buyer.customerUserId ? [ecommerceCustomerUserTag(buyer.customerUserId)] : []),\n ...buyer.customerIds.map(ecommerceCustomerTag),\n ...buyer.customerGroupIds.map(ecommerceCustomerGroupTag),\n ...(tenantId && buyer.customerGroupIds.length === 0 ? [ecommerceUngroupedBuyerTag(tenantId)] : []),\n ]\n}\n\nfunction resolveCacheService(container: CacheContainer | null | undefined): CacheStrategy | null {\n if (!container?.resolve) return null\n try {\n const candidate = container.resolve('cache') as CacheStrategy | null | undefined\n if (\n candidate &&\n typeof candidate.get === 'function' &&\n typeof candidate.set === 'function' &&\n typeof candidate.deleteByTags === 'function'\n ) {\n return candidate\n }\n } catch {\n return null\n }\n return null\n}\n\nfunction reportCacheFailure(operation: string, error: unknown): void {\n logger.warn('Ecommerce cache operation failed; degrading to a cache miss', { operation, err: error })\n getTelemetryRuntime()?.reportError(error, {\n module: 'ecommerce',\n code: 'ecommerce.cache_operation_failed',\n attributes: { operation },\n })\n}\n\ntype TenantScopedCacheOptions = {\n cache: CacheStrategy | null\n tenantId: string | null\n baseTags: string[]\n}\n\nfunction createTenantScopedAccess({ cache, tenantId, baseTags }: TenantScopedCacheOptions) {\n return {\n async get<T>(key: string): Promise<T | null> {\n if (!cache) return null\n try {\n const value = await runWithCacheTenant(tenantId, () => cache.get(key))\n return (value ?? null) as T | null\n } catch (error) {\n reportCacheFailure('get', error)\n return null\n }\n },\n async set<T>(key: string, value: T, ttlMs: number, tags: string[] | undefined): Promise<void> {\n if (!cache) return\n const mergedTags = Array.from(new Set([...baseTags, ...(tags ?? [])]))\n try {\n await runWithCacheTenant(tenantId, () => cache.set(key, value, { ttl: ttlMs, tags: mergedTags }))\n } catch (error) {\n reportCacheFailure('set', error)\n }\n },\n async deleteByTags(tags: string[]): Promise<number> {\n if (!cache || tags.length === 0) return 0\n try {\n return await runWithCacheTenant(tenantId, () => cache.deleteByTags(tags))\n } catch (error) {\n reportCacheFailure('deleteByTags', error)\n return 0\n }\n },\n }\n}\n\nexport function storefrontCache(container: CacheContainer | null | undefined, ctx: StoreContext): StorefrontCache {\n const access = createTenantScopedAccess({\n cache: resolveCacheService(container),\n tenantId: ctx.tenantId,\n baseTags: [ecommerceStoreTag(ctx.store.id)],\n })\n return {\n get: (parts, options) => access.get(buildStorefrontCacheKey(ctx, parts, options)),\n set: (parts, value, options) =>\n access.set(buildStorefrontCacheKey(ctx, parts, { scope: options.scope }), value, options.ttlMs, options.tags),\n deleteByTags: (tags) => access.deleteByTags(tags),\n }\n}\n\nexport function ecommerceResolutionCache(container: CacheContainer | null | undefined): EcommerceResolutionCache {\n const access = createTenantScopedAccess({\n cache: resolveCacheService(container),\n tenantId: null,\n baseTags: [],\n })\n return {\n get: (parts) => access.get(buildEcommerceResolutionCacheKey(parts)),\n set: (parts, value, options) =>\n access.set(buildEcommerceResolutionCacheKey(parts), value, options.ttlMs, options.tags),\n deleteByTags: (tags) => access.deleteByTags(tags),\n }\n}\n\nexport type EcommerceCacheInvalidation = { tenantId: string | null; tags: string[] }\n\n/**\n * Event-driven invalidation (\u00A78): evicts `tags` from the tenant-agnostic resolution cache and, when\n * the tenant is known, from that tenant's storefront and buyer-context caches. Tags are tenant\n * scoped by the cache layer, so each scope is cleared inside its own tenant.\n */\nexport async function invalidateEcommerceCacheTags(\n container: CacheContainer | null | undefined,\n invalidation: EcommerceCacheInvalidation,\n): Promise<number> {\n const tags = Array.from(new Set(invalidation.tags.filter((tag) => tag.length > 0)))\n if (tags.length === 0) return 0\n const cache = resolveCacheService(container)\n if (!cache) return 0\n const tenantIds = invalidation.tenantId ? [null, invalidation.tenantId] : [null]\n let deleted = 0\n for (const tenantId of tenantIds) {\n deleted += await createTenantScopedAccess({ cache, tenantId, baseTags: [] }).deleteByTags(tags)\n }\n return deleted\n}\n\nexport function buyerContextCache(\n container: CacheContainer | null | undefined,\n scope: BuyerContextCacheScope,\n): BuyerContextCache {\n const access = createTenantScopedAccess({\n cache: resolveCacheService(container),\n tenantId: scope.tenantId,\n baseTags: [ecommerceStoreTag(scope.storeId)],\n })\n return {\n get: (customerUserId) => access.get<BuyerContext>(buildBuyerContextCacheKey(scope.storeId, customerUserId)),\n set: (buyer) =>\n access.set(\n buildBuyerContextCacheKey(scope.storeId, buyer.customerUserId),\n buyer,\n BUYER_CONTEXT_TTL_MS,\n buyerContextTags(buyer, scope.tenantId),\n ),\n deleteByTags: (tags) => access.deleteByTags(tags),\n }\n}\n"],
|
|
5
|
+
"mappings": "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",
|
|
6
|
+
"names": []
|
|
7
|
+
}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import { NextResponse } from "next/server";
|
|
2
|
+
import { isCrudHttpError } from "@open-mercato/shared/lib/crud/errors";
|
|
3
|
+
const recheckByRequest = /* @__PURE__ */ new WeakMap();
|
|
4
|
+
function registerCreateConflictRecheck(request, recheck) {
|
|
5
|
+
if (request) recheckByRequest.set(request, recheck);
|
|
6
|
+
}
|
|
7
|
+
function clearCreateConflictRecheck(request) {
|
|
8
|
+
if (request) recheckByRequest.delete(request);
|
|
9
|
+
}
|
|
10
|
+
function withCreateConflictRecheck(handler) {
|
|
11
|
+
return async (request) => {
|
|
12
|
+
const response = await handler(request);
|
|
13
|
+
const recheck = recheckByRequest.get(request);
|
|
14
|
+
recheckByRequest.delete(request);
|
|
15
|
+
if (!recheck || response.status !== 500) return response;
|
|
16
|
+
try {
|
|
17
|
+
await recheck();
|
|
18
|
+
} catch (err) {
|
|
19
|
+
if (isCrudHttpError(err) && err.status === 409) return NextResponse.json(err.body, { status: 409 });
|
|
20
|
+
}
|
|
21
|
+
return response;
|
|
22
|
+
};
|
|
23
|
+
}
|
|
24
|
+
export {
|
|
25
|
+
clearCreateConflictRecheck,
|
|
26
|
+
registerCreateConflictRecheck,
|
|
27
|
+
withCreateConflictRecheck
|
|
28
|
+
};
|
|
29
|
+
//# sourceMappingURL=createConflictRecheck.js.map
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": 3,
|
|
3
|
+
"sources": ["../../../../src/modules/ecommerce/lib/createConflictRecheck.ts"],
|
|
4
|
+
"sourcesContent": ["import { NextResponse } from 'next/server'\nimport { isCrudHttpError } from '@open-mercato/shared/lib/crud/errors'\n\ntype ConflictRecheck = () => Promise<void>\n\nconst recheckByRequest = new WeakMap<Request, ConflictRecheck>()\n\nexport function registerCreateConflictRecheck(request: Request | undefined, recheck: ConflictRecheck): void {\n if (request) recheckByRequest.set(request, recheck)\n}\n\nexport function clearCreateConflictRecheck(request: Request | undefined): void {\n if (request) recheckByRequest.delete(request)\n}\n\nexport function withCreateConflictRecheck(\n handler: (request: Request) => Promise<Response>,\n): (request: Request) => Promise<Response> {\n return async (request: Request): Promise<Response> => {\n const response = await handler(request)\n const recheck = recheckByRequest.get(request)\n recheckByRequest.delete(request)\n if (!recheck || response.status !== 500) return response\n try {\n await recheck()\n } catch (err) {\n if (isCrudHttpError(err) && err.status === 409) return NextResponse.json(err.body, { status: 409 })\n }\n return response\n }\n}\n"],
|
|
5
|
+
"mappings": "AAAA,SAAS,oBAAoB;AAC7B,SAAS,uBAAuB;AAIhC,MAAM,mBAAmB,oBAAI,QAAkC;AAExD,SAAS,8BAA8B,SAA8B,SAAgC;AAC1G,MAAI,QAAS,kBAAiB,IAAI,SAAS,OAAO;AACpD;AAEO,SAAS,2BAA2B,SAAoC;AAC7E,MAAI,QAAS,kBAAiB,OAAO,OAAO;AAC9C;AAEO,SAAS,0BACd,SACyC;AACzC,SAAO,OAAO,YAAwC;AACpD,UAAM,WAAW,MAAM,QAAQ,OAAO;AACtC,UAAM,UAAU,iBAAiB,IAAI,OAAO;AAC5C,qBAAiB,OAAO,OAAO;AAC/B,QAAI,CAAC,WAAW,SAAS,WAAW,IAAK,QAAO;AAChD,QAAI;AACF,YAAM,QAAQ;AAAA,IAChB,SAAS,KAAK;AACZ,UAAI,gBAAgB,GAAG,KAAK,IAAI,WAAW,IAAK,QAAO,aAAa,KAAK,IAAI,MAAM,EAAE,QAAQ,IAAI,CAAC;AAAA,IACpG;AACA,WAAO;AAAA,EACT;AACF;",
|
|
6
|
+
"names": []
|
|
7
|
+
}
|