@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,94 @@
|
|
|
1
|
+
import { canonicalizeResourceTag, invalidateCrudCache } from "@open-mercato/shared/lib/crud/cache";
|
|
2
|
+
import { emitEcommerceEvent } from "../events.js";
|
|
3
|
+
const ECOMMERCE_EVENTS_MODULE = "ecommerce";
|
|
4
|
+
const STORE_EVENT_ENTITY = "store";
|
|
5
|
+
const STORE_DOMAIN_BINDING_EVENT_ENTITY = "store_domain_binding";
|
|
6
|
+
const STORE_CHANNEL_BINDING_EVENT_ENTITY = "store_channel_binding";
|
|
7
|
+
const previousDomainBindingPlacements = /* @__PURE__ */ new WeakMap();
|
|
8
|
+
const previousChannelBindingStores = /* @__PURE__ */ new WeakMap();
|
|
9
|
+
function rememberDomainBindingPlacement(binding) {
|
|
10
|
+
previousDomainBindingPlacements.set(binding, { storeId: binding.storeId, domainMappingId: binding.domainMappingId });
|
|
11
|
+
}
|
|
12
|
+
function rememberChannelBindingStore(binding) {
|
|
13
|
+
previousChannelBindingStores.set(binding, binding.storeId);
|
|
14
|
+
}
|
|
15
|
+
function buildStoreEventPayload(store) {
|
|
16
|
+
return { id: store.id, tenantId: store.tenantId, organizationId: store.organizationId };
|
|
17
|
+
}
|
|
18
|
+
function buildStoreDomainBindingEventPayload(binding) {
|
|
19
|
+
const payload = {
|
|
20
|
+
id: binding.id,
|
|
21
|
+
storeId: binding.storeId,
|
|
22
|
+
domainMappingId: binding.domainMappingId,
|
|
23
|
+
tenantId: binding.tenantId,
|
|
24
|
+
organizationId: binding.organizationId
|
|
25
|
+
};
|
|
26
|
+
const previous = previousDomainBindingPlacements.get(binding);
|
|
27
|
+
if (previous && previous.storeId !== binding.storeId) payload.previousStoreId = previous.storeId;
|
|
28
|
+
if (previous && previous.domainMappingId !== binding.domainMappingId) {
|
|
29
|
+
payload.previousDomainMappingId = previous.domainMappingId;
|
|
30
|
+
}
|
|
31
|
+
return payload;
|
|
32
|
+
}
|
|
33
|
+
function buildStoreChannelBindingEventPayload(binding) {
|
|
34
|
+
const payload = {
|
|
35
|
+
id: binding.id,
|
|
36
|
+
storeId: binding.storeId,
|
|
37
|
+
salesChannelId: binding.salesChannelId,
|
|
38
|
+
tenantId: binding.tenantId,
|
|
39
|
+
organizationId: binding.organizationId
|
|
40
|
+
};
|
|
41
|
+
const previousStoreId = previousChannelBindingStores.get(binding);
|
|
42
|
+
if (previousStoreId && previousStoreId !== binding.storeId) payload.previousStoreId = previousStoreId;
|
|
43
|
+
return payload;
|
|
44
|
+
}
|
|
45
|
+
function resourceKindFor(entity) {
|
|
46
|
+
const raw = `${ECOMMERCE_EVENTS_MODULE}.${entity}`;
|
|
47
|
+
return canonicalizeResourceTag(raw) ?? raw;
|
|
48
|
+
}
|
|
49
|
+
async function announceUpdatedRows(container, entity, eventId, rows, buildPayload) {
|
|
50
|
+
const resourceKind = resourceKindFor(entity);
|
|
51
|
+
for (const row of rows) {
|
|
52
|
+
await invalidateCrudCache(
|
|
53
|
+
container,
|
|
54
|
+
resourceKind,
|
|
55
|
+
{ id: row.id, tenantId: row.tenantId, organizationId: row.organizationId },
|
|
56
|
+
row.tenantId,
|
|
57
|
+
"updated"
|
|
58
|
+
);
|
|
59
|
+
await emitEcommerceEvent(eventId, buildPayload(row), {
|
|
60
|
+
persistent: true,
|
|
61
|
+
tenantId: row.tenantId,
|
|
62
|
+
organizationId: row.organizationId
|
|
63
|
+
});
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
function announceStoresUpdated(container, stores) {
|
|
67
|
+
return announceUpdatedRows(container, STORE_EVENT_ENTITY, "ecommerce.store.updated", stores, buildStoreEventPayload);
|
|
68
|
+
}
|
|
69
|
+
function announceStoreBrandingUpdated(container, store) {
|
|
70
|
+
return announceUpdatedRows(container, STORE_EVENT_ENTITY, "ecommerce.store.branding_updated", [store], buildStoreEventPayload);
|
|
71
|
+
}
|
|
72
|
+
function announceStoreDomainBindingsUpdated(container, bindings) {
|
|
73
|
+
return announceUpdatedRows(container, STORE_DOMAIN_BINDING_EVENT_ENTITY, "ecommerce.store_domain_binding.updated", bindings, buildStoreDomainBindingEventPayload);
|
|
74
|
+
}
|
|
75
|
+
function announceStoreChannelBindingsUpdated(container, bindings) {
|
|
76
|
+
return announceUpdatedRows(container, STORE_CHANNEL_BINDING_EVENT_ENTITY, "ecommerce.store_channel_binding.updated", bindings, buildStoreChannelBindingEventPayload);
|
|
77
|
+
}
|
|
78
|
+
export {
|
|
79
|
+
ECOMMERCE_EVENTS_MODULE,
|
|
80
|
+
STORE_CHANNEL_BINDING_EVENT_ENTITY,
|
|
81
|
+
STORE_DOMAIN_BINDING_EVENT_ENTITY,
|
|
82
|
+
STORE_EVENT_ENTITY,
|
|
83
|
+
announceStoreBrandingUpdated,
|
|
84
|
+
announceStoreChannelBindingsUpdated,
|
|
85
|
+
announceStoreDomainBindingsUpdated,
|
|
86
|
+
announceStoresUpdated,
|
|
87
|
+
buildStoreChannelBindingEventPayload,
|
|
88
|
+
buildStoreDomainBindingEventPayload,
|
|
89
|
+
buildStoreEventPayload,
|
|
90
|
+
rememberChannelBindingStore,
|
|
91
|
+
rememberDomainBindingPlacement,
|
|
92
|
+
resourceKindFor
|
|
93
|
+
};
|
|
94
|
+
//# sourceMappingURL=crudEvents.js.map
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": 3,
|
|
3
|
+
"sources": ["../../../../src/modules/ecommerce/lib/crudEvents.ts"],
|
|
4
|
+
"sourcesContent": ["import type { CrudCtx } from '@open-mercato/shared/lib/crud/factory'\nimport { canonicalizeResourceTag, invalidateCrudCache } from '@open-mercato/shared/lib/crud/cache'\nimport type { EcommerceStore, EcommerceStoreChannelBinding, EcommerceStoreDomainBinding } from '../data/entities'\nimport { emitEcommerceEvent, type EcommerceEventId } from '../events'\n\nexport const ECOMMERCE_EVENTS_MODULE = 'ecommerce'\nexport const STORE_EVENT_ENTITY = 'store'\nexport const STORE_DOMAIN_BINDING_EVENT_ENTITY = 'store_domain_binding'\nexport const STORE_CHANNEL_BINDING_EVENT_ENTITY = 'store_channel_binding'\n\nexport type StoreEventPayload = { id: string; tenantId: string; organizationId: string }\nexport type StoreDomainBindingEventPayload = StoreEventPayload & {\n storeId: string\n domainMappingId: string\n previousStoreId?: string\n previousDomainMappingId?: string\n}\nexport type StoreChannelBindingEventPayload = StoreEventPayload & {\n storeId: string\n salesChannelId: string\n previousStoreId?: string\n}\n\ntype DomainBindingPlacementSnapshot = Pick<EcommerceStoreDomainBinding, 'storeId' | 'domainMappingId'>\n\nconst previousDomainBindingPlacements = new WeakMap<EcommerceStoreDomainBinding, DomainBindingPlacementSnapshot>()\nconst previousChannelBindingStores = new WeakMap<EcommerceStoreChannelBinding, string>()\n\n/**\n * Records where a domain binding pointed before an update, so its `updated` event also names the\n * store and domain mapping it moved away from and their cached storefront entries are evicted.\n */\nexport function rememberDomainBindingPlacement(binding: EcommerceStoreDomainBinding): void {\n previousDomainBindingPlacements.set(binding, { storeId: binding.storeId, domainMappingId: binding.domainMappingId })\n}\n\n/** Channel-binding counterpart of `rememberDomainBindingPlacement`: the store it moved away from. */\nexport function rememberChannelBindingStore(binding: EcommerceStoreChannelBinding): void {\n previousChannelBindingStores.set(binding, binding.storeId)\n}\n\nexport function buildStoreEventPayload(store: EcommerceStore): StoreEventPayload {\n return { id: store.id, tenantId: store.tenantId, organizationId: store.organizationId }\n}\n\nexport function buildStoreDomainBindingEventPayload(binding: EcommerceStoreDomainBinding): StoreDomainBindingEventPayload {\n const payload: StoreDomainBindingEventPayload = {\n id: binding.id,\n storeId: binding.storeId,\n domainMappingId: binding.domainMappingId,\n tenantId: binding.tenantId,\n organizationId: binding.organizationId,\n }\n const previous = previousDomainBindingPlacements.get(binding)\n if (previous && previous.storeId !== binding.storeId) payload.previousStoreId = previous.storeId\n if (previous && previous.domainMappingId !== binding.domainMappingId) {\n payload.previousDomainMappingId = previous.domainMappingId\n }\n return payload\n}\n\nexport function buildStoreChannelBindingEventPayload(\n binding: EcommerceStoreChannelBinding,\n): StoreChannelBindingEventPayload {\n const payload: StoreChannelBindingEventPayload = {\n id: binding.id,\n storeId: binding.storeId,\n salesChannelId: binding.salesChannelId,\n tenantId: binding.tenantId,\n organizationId: binding.organizationId,\n }\n const previousStoreId = previousChannelBindingStores.get(binding)\n if (previousStoreId && previousStoreId !== binding.storeId) payload.previousStoreId = previousStoreId\n return payload\n}\n\nexport function resourceKindFor(entity: string): string {\n const raw = `${ECOMMERCE_EVENTS_MODULE}.${entity}`\n return canonicalizeResourceTag(raw) ?? raw\n}\n\ntype ScopedRow = { id: string; tenantId: string; organizationId: string }\n\nasync function announceUpdatedRows<TRow extends ScopedRow>(\n container: CrudCtx['container'],\n entity: string,\n eventId: EcommerceEventId,\n rows: TRow[],\n buildPayload: (row: TRow) => StoreEventPayload,\n): Promise<void> {\n const resourceKind = resourceKindFor(entity)\n for (const row of rows) {\n await invalidateCrudCache(\n container,\n resourceKind,\n { id: row.id, tenantId: row.tenantId, organizationId: row.organizationId },\n row.tenantId,\n 'updated',\n )\n await emitEcommerceEvent(eventId, buildPayload(row), {\n persistent: true,\n tenantId: row.tenantId,\n organizationId: row.organizationId,\n })\n }\n}\n\nexport function announceStoresUpdated(container: CrudCtx['container'], stores: EcommerceStore[]): Promise<void> {\n return announceUpdatedRows(container, STORE_EVENT_ENTITY, 'ecommerce.store.updated', stores, buildStoreEventPayload)\n}\n\nexport function announceStoreBrandingUpdated(container: CrudCtx['container'], store: EcommerceStore): Promise<void> {\n return announceUpdatedRows(container, STORE_EVENT_ENTITY, 'ecommerce.store.branding_updated', [store], buildStoreEventPayload)\n}\n\nexport function announceStoreDomainBindingsUpdated(\n container: CrudCtx['container'],\n bindings: EcommerceStoreDomainBinding[],\n): Promise<void> {\n return announceUpdatedRows(container, STORE_DOMAIN_BINDING_EVENT_ENTITY, 'ecommerce.store_domain_binding.updated', bindings, buildStoreDomainBindingEventPayload)\n}\n\nexport function announceStoreChannelBindingsUpdated(\n container: CrudCtx['container'],\n bindings: EcommerceStoreChannelBinding[],\n): Promise<void> {\n return announceUpdatedRows(container, STORE_CHANNEL_BINDING_EVENT_ENTITY, 'ecommerce.store_channel_binding.updated', bindings, buildStoreChannelBindingEventPayload)\n}\n"],
|
|
5
|
+
"mappings": "AACA,SAAS,yBAAyB,2BAA2B;AAE7D,SAAS,0BAAiD;AAEnD,MAAM,0BAA0B;AAChC,MAAM,qBAAqB;AAC3B,MAAM,oCAAoC;AAC1C,MAAM,qCAAqC;AAiBlD,MAAM,kCAAkC,oBAAI,QAAqE;AACjH,MAAM,+BAA+B,oBAAI,QAA8C;AAMhF,SAAS,+BAA+B,SAA4C;AACzF,kCAAgC,IAAI,SAAS,EAAE,SAAS,QAAQ,SAAS,iBAAiB,QAAQ,gBAAgB,CAAC;AACrH;AAGO,SAAS,4BAA4B,SAA6C;AACvF,+BAA6B,IAAI,SAAS,QAAQ,OAAO;AAC3D;AAEO,SAAS,uBAAuB,OAA0C;AAC/E,SAAO,EAAE,IAAI,MAAM,IAAI,UAAU,MAAM,UAAU,gBAAgB,MAAM,eAAe;AACxF;AAEO,SAAS,oCAAoC,SAAsE;AACxH,QAAM,UAA0C;AAAA,IAC9C,IAAI,QAAQ;AAAA,IACZ,SAAS,QAAQ;AAAA,IACjB,iBAAiB,QAAQ;AAAA,IACzB,UAAU,QAAQ;AAAA,IAClB,gBAAgB,QAAQ;AAAA,EAC1B;AACA,QAAM,WAAW,gCAAgC,IAAI,OAAO;AAC5D,MAAI,YAAY,SAAS,YAAY,QAAQ,QAAS,SAAQ,kBAAkB,SAAS;AACzF,MAAI,YAAY,SAAS,oBAAoB,QAAQ,iBAAiB;AACpE,YAAQ,0BAA0B,SAAS;AAAA,EAC7C;AACA,SAAO;AACT;AAEO,SAAS,qCACd,SACiC;AACjC,QAAM,UAA2C;AAAA,IAC/C,IAAI,QAAQ;AAAA,IACZ,SAAS,QAAQ;AAAA,IACjB,gBAAgB,QAAQ;AAAA,IACxB,UAAU,QAAQ;AAAA,IAClB,gBAAgB,QAAQ;AAAA,EAC1B;AACA,QAAM,kBAAkB,6BAA6B,IAAI,OAAO;AAChE,MAAI,mBAAmB,oBAAoB,QAAQ,QAAS,SAAQ,kBAAkB;AACtF,SAAO;AACT;AAEO,SAAS,gBAAgB,QAAwB;AACtD,QAAM,MAAM,GAAG,uBAAuB,IAAI,MAAM;AAChD,SAAO,wBAAwB,GAAG,KAAK;AACzC;AAIA,eAAe,oBACb,WACA,QACA,SACA,MACA,cACe;AACf,QAAM,eAAe,gBAAgB,MAAM;AAC3C,aAAW,OAAO,MAAM;AACtB,UAAM;AAAA,MACJ;AAAA,MACA;AAAA,MACA,EAAE,IAAI,IAAI,IAAI,UAAU,IAAI,UAAU,gBAAgB,IAAI,eAAe;AAAA,MACzE,IAAI;AAAA,MACJ;AAAA,IACF;AACA,UAAM,mBAAmB,SAAS,aAAa,GAAG,GAAG;AAAA,MACnD,YAAY;AAAA,MACZ,UAAU,IAAI;AAAA,MACd,gBAAgB,IAAI;AAAA,IACtB,CAAC;AAAA,EACH;AACF;AAEO,SAAS,sBAAsB,WAAiC,QAAyC;AAC9G,SAAO,oBAAoB,WAAW,oBAAoB,2BAA2B,QAAQ,sBAAsB;AACrH;AAEO,SAAS,6BAA6B,WAAiC,OAAsC;AAClH,SAAO,oBAAoB,WAAW,oBAAoB,oCAAoC,CAAC,KAAK,GAAG,sBAAsB;AAC/H;AAEO,SAAS,mCACd,WACA,UACe;AACf,SAAO,oBAAoB,WAAW,mCAAmC,0CAA0C,UAAU,mCAAmC;AAClK;AAEO,SAAS,oCACd,WACA,UACe;AACf,SAAO,oBAAoB,WAAW,oCAAoC,2CAA2C,UAAU,oCAAoC;AACrK;",
|
|
6
|
+
"names": []
|
|
7
|
+
}
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
import { CrudHttpError } from "@open-mercato/shared/lib/crud/errors";
|
|
3
|
+
import { findOneWithDecryption } from "@open-mercato/shared/lib/encryption/find";
|
|
4
|
+
import { resolveTranslations } from "@open-mercato/shared/lib/i18n/server";
|
|
5
|
+
function resolveWriteScope(ctx) {
|
|
6
|
+
const tenantId = ctx.auth?.tenantId ?? null;
|
|
7
|
+
if (!tenantId) throw new CrudHttpError(400, { error: "[internal] ecommerce write scope is missing a tenant id" });
|
|
8
|
+
const organizationId = resolveScopeOrganizationId(ctx);
|
|
9
|
+
if (!organizationId) {
|
|
10
|
+
throw new CrudHttpError(400, { error: "[internal] ecommerce write scope is missing an organization id" });
|
|
11
|
+
}
|
|
12
|
+
return { tenantId, organizationId };
|
|
13
|
+
}
|
|
14
|
+
function resolveScopeOrganizationId(ctx) {
|
|
15
|
+
return ctx.selectedOrganizationId ?? ctx.auth?.orgId ?? null;
|
|
16
|
+
}
|
|
17
|
+
const recordIdSchema = z.string().uuid();
|
|
18
|
+
async function assertRecordInWriteScope(ctx, entityName, id, notFound) {
|
|
19
|
+
const tenantId = ctx.auth?.tenantId ?? null;
|
|
20
|
+
const organizationId = resolveScopeOrganizationId(ctx);
|
|
21
|
+
if (typeof id !== "string" || !recordIdSchema.safeParse(id).success || !tenantId || !organizationId) return;
|
|
22
|
+
const em = ctx.container.resolve("em").fork();
|
|
23
|
+
const record = await findOneWithDecryption(
|
|
24
|
+
em,
|
|
25
|
+
entityName,
|
|
26
|
+
{ id, tenantId, deletedAt: null },
|
|
27
|
+
void 0,
|
|
28
|
+
{ tenantId, organizationId }
|
|
29
|
+
);
|
|
30
|
+
if (!record || record.organizationId === organizationId) return;
|
|
31
|
+
throw new CrudHttpError(404, { error: await translateEcommerceError(notFound.key, notFound.fallback) });
|
|
32
|
+
}
|
|
33
|
+
function hasOwn(input, key) {
|
|
34
|
+
return Object.prototype.hasOwnProperty.call(input, key);
|
|
35
|
+
}
|
|
36
|
+
async function translateEcommerceError(key, fallback) {
|
|
37
|
+
try {
|
|
38
|
+
const { translate } = await resolveTranslations();
|
|
39
|
+
return translate(key, fallback);
|
|
40
|
+
} catch {
|
|
41
|
+
return fallback;
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
const ECOMMERCE_INTERNAL_ERROR_KEY = "ecommerce.errors.internal";
|
|
45
|
+
const ECOMMERCE_INTERNAL_ERROR_FALLBACK = "Something went wrong. Try again.";
|
|
46
|
+
async function ecommerceInternalErrorBody() {
|
|
47
|
+
return { error: await translateEcommerceError(ECOMMERCE_INTERNAL_ERROR_KEY, ECOMMERCE_INTERNAL_ERROR_FALLBACK) };
|
|
48
|
+
}
|
|
49
|
+
function fieldError(status, fieldErrors) {
|
|
50
|
+
const [message] = Object.values(fieldErrors);
|
|
51
|
+
return new CrudHttpError(status, { error: message, fieldErrors });
|
|
52
|
+
}
|
|
53
|
+
export {
|
|
54
|
+
ECOMMERCE_INTERNAL_ERROR_FALLBACK,
|
|
55
|
+
ECOMMERCE_INTERNAL_ERROR_KEY,
|
|
56
|
+
assertRecordInWriteScope,
|
|
57
|
+
ecommerceInternalErrorBody,
|
|
58
|
+
fieldError,
|
|
59
|
+
hasOwn,
|
|
60
|
+
resolveScopeOrganizationId,
|
|
61
|
+
resolveWriteScope,
|
|
62
|
+
translateEcommerceError
|
|
63
|
+
};
|
|
64
|
+
//# sourceMappingURL=crudSupport.js.map
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": 3,
|
|
3
|
+
"sources": ["../../../../src/modules/ecommerce/lib/crudSupport.ts"],
|
|
4
|
+
"sourcesContent": ["import { z } from 'zod'\nimport type { EntityManager } from '@mikro-orm/postgresql'\nimport type { EntityName, FilterQuery } from '@mikro-orm/core'\nimport type { CrudCtx } from '@open-mercato/shared/lib/crud/factory'\nimport { CrudHttpError } from '@open-mercato/shared/lib/crud/errors'\nimport { findOneWithDecryption } from '@open-mercato/shared/lib/encryption/find'\nimport { resolveTranslations } from '@open-mercato/shared/lib/i18n/server'\n\nexport type Translate = (key: string, fallback?: string) => string\n\nexport type EcommerceWriteScope = { tenantId: string; organizationId: string }\n\nexport function resolveWriteScope(ctx: CrudCtx): EcommerceWriteScope {\n const tenantId = ctx.auth?.tenantId ?? null\n if (!tenantId) throw new CrudHttpError(400, { error: '[internal] ecommerce write scope is missing a tenant id' })\n const organizationId = resolveScopeOrganizationId(ctx)\n if (!organizationId) {\n throw new CrudHttpError(400, { error: '[internal] ecommerce write scope is missing an organization id' })\n }\n return { tenantId, organizationId }\n}\n\nexport function resolveScopeOrganizationId(ctx: CrudCtx): string | null {\n return ctx.selectedOrganizationId ?? ctx.auth?.orgId ?? null\n}\n\nconst recordIdSchema = z.string().uuid()\n\ntype ScopedRecord = { id: string; tenantId: string; organizationId: string; deletedAt?: Date | null }\n\n/**\n * Writes, reference checks, enrichers and the branding routes all work in the single organization of\n * `resolveWriteScope`, while the CRUD factory also admits descendant organizations. Updates and\n * deletes are held to that organization too, so a parent selection cannot half-edit a child\n * organization's record; a record outside it answers the same 404 as a missing one.\n */\nexport async function assertRecordInWriteScope<T extends ScopedRecord>(\n ctx: CrudCtx,\n entityName: EntityName<T>,\n id: unknown,\n notFound: { key: string; fallback: string },\n): Promise<void> {\n const tenantId = ctx.auth?.tenantId ?? null\n const organizationId = resolveScopeOrganizationId(ctx)\n if (typeof id !== 'string' || !recordIdSchema.safeParse(id).success || !tenantId || !organizationId) return\n const em = (ctx.container.resolve('em') as EntityManager).fork()\n const record = await findOneWithDecryption(\n em,\n entityName,\n { id, tenantId, deletedAt: null } as FilterQuery<T>,\n undefined,\n { tenantId, organizationId },\n )\n if (!record || record.organizationId === organizationId) return\n throw new CrudHttpError(404, { error: await translateEcommerceError(notFound.key, notFound.fallback) })\n}\n\nexport function hasOwn(input: object, key: string): boolean {\n return Object.prototype.hasOwnProperty.call(input, key)\n}\n\n/**\n * A client-facing error message from the `ecommerce.errors.*` catalogue, falling back to `fallback`\n * when translations cannot be loaded (route catch blocks must still answer).\n */\nexport async function translateEcommerceError(key: string, fallback: string): Promise<string> {\n try {\n const { translate } = await resolveTranslations()\n return translate(key, fallback)\n } catch {\n return fallback\n }\n}\n\nexport const ECOMMERCE_INTERNAL_ERROR_KEY = 'ecommerce.errors.internal'\nexport const ECOMMERCE_INTERNAL_ERROR_FALLBACK = 'Something went wrong. Try again.'\n\nexport async function ecommerceInternalErrorBody(): Promise<{ error: string }> {\n return { error: await translateEcommerceError(ECOMMERCE_INTERNAL_ERROR_KEY, ECOMMERCE_INTERNAL_ERROR_FALLBACK) }\n}\n\nexport function fieldError(status: number, fieldErrors: Record<string, string>): CrudHttpError {\n const [message] = Object.values(fieldErrors)\n return new CrudHttpError(status, { error: message, fieldErrors })\n}\n"],
|
|
5
|
+
"mappings": "AAAA,SAAS,SAAS;AAIlB,SAAS,qBAAqB;AAC9B,SAAS,6BAA6B;AACtC,SAAS,2BAA2B;AAM7B,SAAS,kBAAkB,KAAmC;AACnE,QAAM,WAAW,IAAI,MAAM,YAAY;AACvC,MAAI,CAAC,SAAU,OAAM,IAAI,cAAc,KAAK,EAAE,OAAO,0DAA0D,CAAC;AAChH,QAAM,iBAAiB,2BAA2B,GAAG;AACrD,MAAI,CAAC,gBAAgB;AACnB,UAAM,IAAI,cAAc,KAAK,EAAE,OAAO,iEAAiE,CAAC;AAAA,EAC1G;AACA,SAAO,EAAE,UAAU,eAAe;AACpC;AAEO,SAAS,2BAA2B,KAA6B;AACtE,SAAO,IAAI,0BAA0B,IAAI,MAAM,SAAS;AAC1D;AAEA,MAAM,iBAAiB,EAAE,OAAO,EAAE,KAAK;AAUvC,eAAsB,yBACpB,KACA,YACA,IACA,UACe;AACf,QAAM,WAAW,IAAI,MAAM,YAAY;AACvC,QAAM,iBAAiB,2BAA2B,GAAG;AACrD,MAAI,OAAO,OAAO,YAAY,CAAC,eAAe,UAAU,EAAE,EAAE,WAAW,CAAC,YAAY,CAAC,eAAgB;AACrG,QAAM,KAAM,IAAI,UAAU,QAAQ,IAAI,EAAoB,KAAK;AAC/D,QAAM,SAAS,MAAM;AAAA,IACnB;AAAA,IACA;AAAA,IACA,EAAE,IAAI,UAAU,WAAW,KAAK;AAAA,IAChC;AAAA,IACA,EAAE,UAAU,eAAe;AAAA,EAC7B;AACA,MAAI,CAAC,UAAU,OAAO,mBAAmB,eAAgB;AACzD,QAAM,IAAI,cAAc,KAAK,EAAE,OAAO,MAAM,wBAAwB,SAAS,KAAK,SAAS,QAAQ,EAAE,CAAC;AACxG;AAEO,SAAS,OAAO,OAAe,KAAsB;AAC1D,SAAO,OAAO,UAAU,eAAe,KAAK,OAAO,GAAG;AACxD;AAMA,eAAsB,wBAAwB,KAAa,UAAmC;AAC5F,MAAI;AACF,UAAM,EAAE,UAAU,IAAI,MAAM,oBAAoB;AAChD,WAAO,UAAU,KAAK,QAAQ;AAAA,EAChC,QAAQ;AACN,WAAO;AAAA,EACT;AACF;AAEO,MAAM,+BAA+B;AACrC,MAAM,oCAAoC;AAEjD,eAAsB,6BAAyD;AAC7E,SAAO,EAAE,OAAO,MAAM,wBAAwB,8BAA8B,iCAAiC,EAAE;AACjH;AAEO,SAAS,WAAW,QAAgB,aAAoD;AAC7F,QAAM,CAAC,OAAO,IAAI,OAAO,OAAO,WAAW;AAC3C,SAAO,IAAI,cAAc,QAAQ,EAAE,OAAO,SAAS,YAAY,CAAC;AAClE;",
|
|
6
|
+
"names": []
|
|
7
|
+
}
|
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
import { invalidateCrudCache } from "@open-mercato/shared/lib/crud/cache";
|
|
2
|
+
import { findWithDecryption } from "@open-mercato/shared/lib/encryption/find";
|
|
3
|
+
import { createLogger } from "@open-mercato/shared/lib/logger";
|
|
4
|
+
import { EcommerceStoreDomainBinding } from "../data/entities.js";
|
|
5
|
+
import { resourceKindFor, STORE_DOMAIN_BINDING_EVENT_ENTITY } from "./crudEvents.js";
|
|
6
|
+
import { domainBindingEvents, domainBindingIndexer } from "./storeBindingCascade.js";
|
|
7
|
+
import {
|
|
8
|
+
asPayloadRecord,
|
|
9
|
+
readEventScope,
|
|
10
|
+
readPayloadString,
|
|
11
|
+
tryResolveService
|
|
12
|
+
} from "./subscriberSupport.js";
|
|
13
|
+
const logger = createLogger("ecommerce").child({ component: "domain-binding-rebind" });
|
|
14
|
+
function readDomainMappingReplacement(payload, ctx) {
|
|
15
|
+
const record = asPayloadRecord(payload);
|
|
16
|
+
const domainMappingId = readPayloadString(record, "id");
|
|
17
|
+
const replacedDomainMappingId = readPayloadString(record, "replacedDomainId");
|
|
18
|
+
const { tenantId, organizationId } = readEventScope(record, ctx);
|
|
19
|
+
if (!domainMappingId || !replacedDomainMappingId || !tenantId || !organizationId) return null;
|
|
20
|
+
if (domainMappingId === replacedDomainMappingId) return null;
|
|
21
|
+
return { domainMappingId, replacedDomainMappingId, tenantId, organizationId };
|
|
22
|
+
}
|
|
23
|
+
function prefixKey(binding) {
|
|
24
|
+
return binding.pathPrefix ?? "";
|
|
25
|
+
}
|
|
26
|
+
function findLiveBindings(em, domainMappingId, replacement) {
|
|
27
|
+
const scope = { tenantId: replacement.tenantId, organizationId: replacement.organizationId };
|
|
28
|
+
return findWithDecryption(
|
|
29
|
+
em,
|
|
30
|
+
EcommerceStoreDomainBinding,
|
|
31
|
+
{ domainMappingId, ...scope, deletedAt: null },
|
|
32
|
+
void 0,
|
|
33
|
+
scope
|
|
34
|
+
);
|
|
35
|
+
}
|
|
36
|
+
async function announceRebound(ctx, bindings) {
|
|
37
|
+
const dataEngine = tryResolveService(ctx, "dataEngine");
|
|
38
|
+
if (dataEngine) {
|
|
39
|
+
for (const binding of bindings) {
|
|
40
|
+
dataEngine.markOrmEntityChange({
|
|
41
|
+
action: "updated",
|
|
42
|
+
entity: binding,
|
|
43
|
+
identifiers: { id: binding.id, tenantId: binding.tenantId, organizationId: binding.organizationId },
|
|
44
|
+
events: domainBindingEvents,
|
|
45
|
+
indexer: domainBindingIndexer
|
|
46
|
+
});
|
|
47
|
+
}
|
|
48
|
+
await dataEngine.flushOrmEntityChanges();
|
|
49
|
+
}
|
|
50
|
+
const container = { resolve: ctx.resolve };
|
|
51
|
+
const resourceKind = resourceKindFor(STORE_DOMAIN_BINDING_EVENT_ENTITY);
|
|
52
|
+
for (const binding of bindings) {
|
|
53
|
+
await invalidateCrudCache(
|
|
54
|
+
container,
|
|
55
|
+
resourceKind,
|
|
56
|
+
{ id: binding.id, tenantId: binding.tenantId, organizationId: binding.organizationId },
|
|
57
|
+
binding.tenantId,
|
|
58
|
+
"updated"
|
|
59
|
+
);
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
async function rebindReplacedDomainMapping(ctx, replacement) {
|
|
63
|
+
const rootEm = tryResolveService(ctx, "em");
|
|
64
|
+
if (!rootEm) throw new Error("[internal] ecommerce domain re-binding requires the DI entity manager");
|
|
65
|
+
const em = rootEm.fork();
|
|
66
|
+
const superseded = await findLiveBindings(em, replacement.replacedDomainMappingId, replacement);
|
|
67
|
+
if (superseded.length === 0) return { rebound: [], skipped: [] };
|
|
68
|
+
const existing = await findLiveBindings(em, replacement.domainMappingId, replacement);
|
|
69
|
+
const takenPrefixes = new Set(existing.map(prefixKey));
|
|
70
|
+
const rebound = [];
|
|
71
|
+
const skipped = [];
|
|
72
|
+
for (const binding of superseded) {
|
|
73
|
+
const key = prefixKey(binding);
|
|
74
|
+
if (takenPrefixes.has(key)) {
|
|
75
|
+
skipped.push(binding);
|
|
76
|
+
continue;
|
|
77
|
+
}
|
|
78
|
+
takenPrefixes.add(key);
|
|
79
|
+
binding.domainMappingId = replacement.domainMappingId;
|
|
80
|
+
rebound.push(binding);
|
|
81
|
+
}
|
|
82
|
+
if (rebound.length > 0) await em.flush();
|
|
83
|
+
for (const binding of skipped) {
|
|
84
|
+
logger.warn("Domain binding not re-pointed: path prefix already bound on the replacement mapping", {
|
|
85
|
+
bindingId: binding.id,
|
|
86
|
+
storeId: binding.storeId,
|
|
87
|
+
pathPrefix: binding.pathPrefix ?? null,
|
|
88
|
+
domainMappingId: replacement.domainMappingId,
|
|
89
|
+
replacedDomainMappingId: replacement.replacedDomainMappingId
|
|
90
|
+
});
|
|
91
|
+
}
|
|
92
|
+
if (rebound.length > 0) await announceRebound(ctx, rebound);
|
|
93
|
+
return { rebound, skipped };
|
|
94
|
+
}
|
|
95
|
+
export {
|
|
96
|
+
readDomainMappingReplacement,
|
|
97
|
+
rebindReplacedDomainMapping
|
|
98
|
+
};
|
|
99
|
+
//# sourceMappingURL=domainBindingRebind.js.map
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": 3,
|
|
3
|
+
"sources": ["../../../../src/modules/ecommerce/lib/domainBindingRebind.ts"],
|
|
4
|
+
"sourcesContent": ["import type { EntityManager, FilterQuery } from '@mikro-orm/postgresql'\nimport type { AwilixContainer } from 'awilix'\nimport type { DataEngine } from '@open-mercato/shared/lib/data/engine'\nimport { invalidateCrudCache } from '@open-mercato/shared/lib/crud/cache'\nimport { findWithDecryption } from '@open-mercato/shared/lib/encryption/find'\nimport { createLogger } from '@open-mercato/shared/lib/logger'\nimport { EcommerceStoreDomainBinding } from '../data/entities'\nimport { resourceKindFor, STORE_DOMAIN_BINDING_EVENT_ENTITY } from './crudEvents'\nimport { domainBindingEvents, domainBindingIndexer } from './storeBindingCascade'\nimport {\n asPayloadRecord,\n readEventScope,\n readPayloadString,\n tryResolveService,\n type EcommerceSubscriberContext,\n} from './subscriberSupport'\n\n/**\n * Follows `customer_accounts` domain supersession (SPEC-029 \u00A75.2, D5): when a replacement mapping\n * becomes active, `customer_accounts.domain_mapping.replaced` carries the new mapping as `id` and the\n * superseded one as `replacedDomainId`. Every live binding on the superseded id is re-pointed to the\n * new id within the same tenant and organization. Re-running is a no-op because no binding is left\n * on the superseded id; a binding whose `path_prefix` is already taken on the new mapping stays on\n * the superseded id (dangling, R3) and is logged.\n */\n\nexport type DomainMappingReplacement = {\n domainMappingId: string\n replacedDomainMappingId: string\n tenantId: string\n organizationId: string\n}\n\nexport type DomainBindingRebindResult = {\n rebound: EcommerceStoreDomainBinding[]\n skipped: EcommerceStoreDomainBinding[]\n}\n\nconst logger = createLogger('ecommerce').child({ component: 'domain-binding-rebind' })\n\nexport function readDomainMappingReplacement(\n payload: unknown,\n ctx: EcommerceSubscriberContext,\n): DomainMappingReplacement | null {\n const record = asPayloadRecord(payload)\n const domainMappingId = readPayloadString(record, 'id')\n const replacedDomainMappingId = readPayloadString(record, 'replacedDomainId')\n const { tenantId, organizationId } = readEventScope(record, ctx)\n if (!domainMappingId || !replacedDomainMappingId || !tenantId || !organizationId) return null\n if (domainMappingId === replacedDomainMappingId) return null\n return { domainMappingId, replacedDomainMappingId, tenantId, organizationId }\n}\n\nfunction prefixKey(binding: EcommerceStoreDomainBinding): string {\n return binding.pathPrefix ?? ''\n}\n\nfunction findLiveBindings(\n em: EntityManager,\n domainMappingId: string,\n replacement: DomainMappingReplacement,\n): Promise<EcommerceStoreDomainBinding[]> {\n const scope = { tenantId: replacement.tenantId, organizationId: replacement.organizationId }\n return findWithDecryption(\n em,\n EcommerceStoreDomainBinding,\n { domainMappingId, ...scope, deletedAt: null } as FilterQuery<EcommerceStoreDomainBinding>,\n undefined,\n scope,\n )\n}\n\nasync function announceRebound(\n ctx: EcommerceSubscriberContext,\n bindings: EcommerceStoreDomainBinding[],\n): Promise<void> {\n const dataEngine = tryResolveService<DataEngine>(ctx, 'dataEngine')\n if (dataEngine) {\n for (const binding of bindings) {\n dataEngine.markOrmEntityChange({\n action: 'updated',\n entity: binding,\n identifiers: { id: binding.id, tenantId: binding.tenantId, organizationId: binding.organizationId },\n events: domainBindingEvents,\n indexer: domainBindingIndexer,\n })\n }\n await dataEngine.flushOrmEntityChanges()\n }\n const container = { resolve: ctx.resolve } as unknown as AwilixContainer\n const resourceKind = resourceKindFor(STORE_DOMAIN_BINDING_EVENT_ENTITY)\n for (const binding of bindings) {\n await invalidateCrudCache(\n container,\n resourceKind,\n { id: binding.id, tenantId: binding.tenantId, organizationId: binding.organizationId },\n binding.tenantId,\n 'updated',\n )\n }\n}\n\nexport async function rebindReplacedDomainMapping(\n ctx: EcommerceSubscriberContext,\n replacement: DomainMappingReplacement,\n): Promise<DomainBindingRebindResult> {\n const rootEm = tryResolveService<EntityManager>(ctx, 'em')\n if (!rootEm) throw new Error('[internal] ecommerce domain re-binding requires the DI entity manager')\n const em = rootEm.fork()\n const superseded = await findLiveBindings(em, replacement.replacedDomainMappingId, replacement)\n if (superseded.length === 0) return { rebound: [], skipped: [] }\n const existing = await findLiveBindings(em, replacement.domainMappingId, replacement)\n const takenPrefixes = new Set(existing.map(prefixKey))\n const rebound: EcommerceStoreDomainBinding[] = []\n const skipped: EcommerceStoreDomainBinding[] = []\n for (const binding of superseded) {\n const key = prefixKey(binding)\n if (takenPrefixes.has(key)) {\n skipped.push(binding)\n continue\n }\n takenPrefixes.add(key)\n binding.domainMappingId = replacement.domainMappingId\n rebound.push(binding)\n }\n if (rebound.length > 0) await em.flush()\n for (const binding of skipped) {\n logger.warn('Domain binding not re-pointed: path prefix already bound on the replacement mapping', {\n bindingId: binding.id,\n storeId: binding.storeId,\n pathPrefix: binding.pathPrefix ?? null,\n domainMappingId: replacement.domainMappingId,\n replacedDomainMappingId: replacement.replacedDomainMappingId,\n })\n }\n if (rebound.length > 0) await announceRebound(ctx, rebound)\n return { rebound, skipped }\n}\n"],
|
|
5
|
+
"mappings": "AAGA,SAAS,2BAA2B;AACpC,SAAS,0BAA0B;AACnC,SAAS,oBAAoB;AAC7B,SAAS,mCAAmC;AAC5C,SAAS,iBAAiB,yCAAyC;AACnE,SAAS,qBAAqB,4BAA4B;AAC1D;AAAA,EACE;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,OAEK;AAuBP,MAAM,SAAS,aAAa,WAAW,EAAE,MAAM,EAAE,WAAW,wBAAwB,CAAC;AAE9E,SAAS,6BACd,SACA,KACiC;AACjC,QAAM,SAAS,gBAAgB,OAAO;AACtC,QAAM,kBAAkB,kBAAkB,QAAQ,IAAI;AACtD,QAAM,0BAA0B,kBAAkB,QAAQ,kBAAkB;AAC5E,QAAM,EAAE,UAAU,eAAe,IAAI,eAAe,QAAQ,GAAG;AAC/D,MAAI,CAAC,mBAAmB,CAAC,2BAA2B,CAAC,YAAY,CAAC,eAAgB,QAAO;AACzF,MAAI,oBAAoB,wBAAyB,QAAO;AACxD,SAAO,EAAE,iBAAiB,yBAAyB,UAAU,eAAe;AAC9E;AAEA,SAAS,UAAU,SAA8C;AAC/D,SAAO,QAAQ,cAAc;AAC/B;AAEA,SAAS,iBACP,IACA,iBACA,aACwC;AACxC,QAAM,QAAQ,EAAE,UAAU,YAAY,UAAU,gBAAgB,YAAY,eAAe;AAC3F,SAAO;AAAA,IACL;AAAA,IACA;AAAA,IACA,EAAE,iBAAiB,GAAG,OAAO,WAAW,KAAK;AAAA,IAC7C;AAAA,IACA;AAAA,EACF;AACF;AAEA,eAAe,gBACb,KACA,UACe;AACf,QAAM,aAAa,kBAA8B,KAAK,YAAY;AAClE,MAAI,YAAY;AACd,eAAW,WAAW,UAAU;AAC9B,iBAAW,oBAAoB;AAAA,QAC7B,QAAQ;AAAA,QACR,QAAQ;AAAA,QACR,aAAa,EAAE,IAAI,QAAQ,IAAI,UAAU,QAAQ,UAAU,gBAAgB,QAAQ,eAAe;AAAA,QAClG,QAAQ;AAAA,QACR,SAAS;AAAA,MACX,CAAC;AAAA,IACH;AACA,UAAM,WAAW,sBAAsB;AAAA,EACzC;AACA,QAAM,YAAY,EAAE,SAAS,IAAI,QAAQ;AACzC,QAAM,eAAe,gBAAgB,iCAAiC;AACtE,aAAW,WAAW,UAAU;AAC9B,UAAM;AAAA,MACJ;AAAA,MACA;AAAA,MACA,EAAE,IAAI,QAAQ,IAAI,UAAU,QAAQ,UAAU,gBAAgB,QAAQ,eAAe;AAAA,MACrF,QAAQ;AAAA,MACR;AAAA,IACF;AAAA,EACF;AACF;AAEA,eAAsB,4BACpB,KACA,aACoC;AACpC,QAAM,SAAS,kBAAiC,KAAK,IAAI;AACzD,MAAI,CAAC,OAAQ,OAAM,IAAI,MAAM,uEAAuE;AACpG,QAAM,KAAK,OAAO,KAAK;AACvB,QAAM,aAAa,MAAM,iBAAiB,IAAI,YAAY,yBAAyB,WAAW;AAC9F,MAAI,WAAW,WAAW,EAAG,QAAO,EAAE,SAAS,CAAC,GAAG,SAAS,CAAC,EAAE;AAC/D,QAAM,WAAW,MAAM,iBAAiB,IAAI,YAAY,iBAAiB,WAAW;AACpF,QAAM,gBAAgB,IAAI,IAAI,SAAS,IAAI,SAAS,CAAC;AACrD,QAAM,UAAyC,CAAC;AAChD,QAAM,UAAyC,CAAC;AAChD,aAAW,WAAW,YAAY;AAChC,UAAM,MAAM,UAAU,OAAO;AAC7B,QAAI,cAAc,IAAI,GAAG,GAAG;AAC1B,cAAQ,KAAK,OAAO;AACpB;AAAA,IACF;AACA,kBAAc,IAAI,GAAG;AACrB,YAAQ,kBAAkB,YAAY;AACtC,YAAQ,KAAK,OAAO;AAAA,EACtB;AACA,MAAI,QAAQ,SAAS,EAAG,OAAM,GAAG,MAAM;AACvC,aAAW,WAAW,SAAS;AAC7B,WAAO,KAAK,uFAAuF;AAAA,MACjG,WAAW,QAAQ;AAAA,MACnB,SAAS,QAAQ;AAAA,MACjB,YAAY,QAAQ,cAAc;AAAA,MAClC,iBAAiB,YAAY;AAAA,MAC7B,yBAAyB,YAAY;AAAA,IACvC,CAAC;AAAA,EACH;AACA,MAAI,QAAQ,SAAS,EAAG,OAAM,gBAAgB,KAAK,OAAO;AAC1D,SAAO,EAAE,SAAS,QAAQ;AAC5B;",
|
|
6
|
+
"names": []
|
|
7
|
+
}
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
const DOMAIN_MAPPING_STATUSES = ["pending", "verified", "active", "dns_failed", "tls_failed"];
|
|
2
|
+
function isDomainMappingStatus(value) {
|
|
3
|
+
return typeof value === "string" && DOMAIN_MAPPING_STATUSES.includes(value);
|
|
4
|
+
}
|
|
5
|
+
function toIsoString(value) {
|
|
6
|
+
if (!value) return null;
|
|
7
|
+
const date = value instanceof Date ? value : new Date(value);
|
|
8
|
+
return Number.isNaN(date.getTime()) ? null : date.toISOString();
|
|
9
|
+
}
|
|
10
|
+
function toSummary(record) {
|
|
11
|
+
return {
|
|
12
|
+
id: record.id,
|
|
13
|
+
hostname: record.hostname,
|
|
14
|
+
status: record.status ?? "pending",
|
|
15
|
+
lastDnsCheckAt: toIsoString(record.lastDnsCheckAt),
|
|
16
|
+
dnsFailureReason: record.dnsFailureReason ?? null,
|
|
17
|
+
tlsFailureReason: record.tlsFailureReason ?? null
|
|
18
|
+
};
|
|
19
|
+
}
|
|
20
|
+
function resolveDomainMappingReader(container) {
|
|
21
|
+
try {
|
|
22
|
+
const service = container.resolve("domainMappingService");
|
|
23
|
+
return service && typeof service.findByOrganization === "function" ? service : null;
|
|
24
|
+
} catch {
|
|
25
|
+
return null;
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
async function loadOrganizationDomainMappings(container, scope) {
|
|
29
|
+
const reader = resolveDomainMappingReader(container);
|
|
30
|
+
if (!reader) return null;
|
|
31
|
+
const records = await reader.findByOrganization(scope.organizationId, { tenantId: scope.tenantId });
|
|
32
|
+
return records.filter((record) => record.tenantId === scope.tenantId && record.organizationId === scope.organizationId).map(toSummary);
|
|
33
|
+
}
|
|
34
|
+
export {
|
|
35
|
+
DOMAIN_MAPPING_STATUSES,
|
|
36
|
+
isDomainMappingStatus,
|
|
37
|
+
loadOrganizationDomainMappings
|
|
38
|
+
};
|
|
39
|
+
//# sourceMappingURL=domainMappingSummaries.js.map
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": 3,
|
|
3
|
+
"sources": ["../../../../src/modules/ecommerce/lib/domainMappingSummaries.ts"],
|
|
4
|
+
"sourcesContent": ["export const DOMAIN_MAPPING_STATUSES = ['pending', 'verified', 'active', 'dns_failed', 'tls_failed'] as const\n\nexport type DomainMappingStatus = (typeof DOMAIN_MAPPING_STATUSES)[number]\n\nexport type DomainMappingSummary = {\n id: string\n hostname: string\n status: string\n lastDnsCheckAt: string | null\n dnsFailureReason: string | null\n tlsFailureReason: string | null\n}\n\ntype DomainMappingRecord = {\n id: string\n hostname: string\n organizationId: string\n tenantId: string\n status?: string | null\n lastDnsCheckAt?: Date | string | null\n dnsFailureReason?: string | null\n tlsFailureReason?: string | null\n}\n\ntype DomainMappingReader = {\n findByOrganization(organizationId: string, scope?: { tenantId?: string }): Promise<DomainMappingRecord[]>\n}\n\ntype ContainerLike = { resolve(name: string): unknown }\n\nexport function isDomainMappingStatus(value: unknown): value is DomainMappingStatus {\n return typeof value === 'string' && (DOMAIN_MAPPING_STATUSES as readonly string[]).includes(value)\n}\n\nfunction toIsoString(value: Date | string | null | undefined): string | null {\n if (!value) return null\n const date = value instanceof Date ? value : new Date(value)\n return Number.isNaN(date.getTime()) ? null : date.toISOString()\n}\n\nfunction toSummary(record: DomainMappingRecord): DomainMappingSummary {\n return {\n id: record.id,\n hostname: record.hostname,\n status: record.status ?? 'pending',\n lastDnsCheckAt: toIsoString(record.lastDnsCheckAt),\n dnsFailureReason: record.dnsFailureReason ?? null,\n tlsFailureReason: record.tlsFailureReason ?? null,\n }\n}\n\nfunction resolveDomainMappingReader(container: unknown): DomainMappingReader | null {\n try {\n const service = (container as ContainerLike).resolve('domainMappingService') as DomainMappingReader | null | undefined\n return service && typeof service.findByOrganization === 'function' ? service : null\n } catch {\n return null\n }\n}\n\nexport async function loadOrganizationDomainMappings(\n container: unknown,\n scope: { organizationId: string; tenantId: string },\n): Promise<DomainMappingSummary[] | null> {\n const reader = resolveDomainMappingReader(container)\n if (!reader) return null\n const records = await reader.findByOrganization(scope.organizationId, { tenantId: scope.tenantId })\n return records\n .filter((record) => record.tenantId === scope.tenantId && record.organizationId === scope.organizationId)\n .map(toSummary)\n}\n"],
|
|
5
|
+
"mappings": "AAAO,MAAM,0BAA0B,CAAC,WAAW,YAAY,UAAU,cAAc,YAAY;AA8B5F,SAAS,sBAAsB,OAA8C;AAClF,SAAO,OAAO,UAAU,YAAa,wBAA8C,SAAS,KAAK;AACnG;AAEA,SAAS,YAAY,OAAwD;AAC3E,MAAI,CAAC,MAAO,QAAO;AACnB,QAAM,OAAO,iBAAiB,OAAO,QAAQ,IAAI,KAAK,KAAK;AAC3D,SAAO,OAAO,MAAM,KAAK,QAAQ,CAAC,IAAI,OAAO,KAAK,YAAY;AAChE;AAEA,SAAS,UAAU,QAAmD;AACpE,SAAO;AAAA,IACL,IAAI,OAAO;AAAA,IACX,UAAU,OAAO;AAAA,IACjB,QAAQ,OAAO,UAAU;AAAA,IACzB,gBAAgB,YAAY,OAAO,cAAc;AAAA,IACjD,kBAAkB,OAAO,oBAAoB;AAAA,IAC7C,kBAAkB,OAAO,oBAAoB;AAAA,EAC/C;AACF;AAEA,SAAS,2BAA2B,WAAgD;AAClF,MAAI;AACF,UAAM,UAAW,UAA4B,QAAQ,sBAAsB;AAC3E,WAAO,WAAW,OAAO,QAAQ,uBAAuB,aAAa,UAAU;AAAA,EACjF,QAAQ;AACN,WAAO;AAAA,EACT;AACF;AAEA,eAAsB,+BACpB,WACA,OACwC;AACxC,QAAM,SAAS,2BAA2B,SAAS;AACnD,MAAI,CAAC,OAAQ,QAAO;AACpB,QAAM,UAAU,MAAM,OAAO,mBAAmB,MAAM,gBAAgB,EAAE,UAAU,MAAM,SAAS,CAAC;AAClG,SAAO,QACJ,OAAO,CAAC,WAAW,OAAO,aAAa,MAAM,YAAY,OAAO,mBAAmB,MAAM,cAAc,EACvG,IAAI,SAAS;AAClB;",
|
|
6
|
+
"names": []
|
|
7
|
+
}
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
import { isUniqueViolation } from "@open-mercato/shared/lib/crud/errors";
|
|
2
|
+
import { findWithDecryption } from "@open-mercato/shared/lib/encryption/find";
|
|
3
|
+
const PROMOTION_ATTEMPTS = 3;
|
|
4
|
+
function flaggedRowsWhere(config, scope, keepId) {
|
|
5
|
+
const where = { ...scope, [config.flag]: true, deletedAt: null };
|
|
6
|
+
if (keepId) where.id = { $ne: keepId };
|
|
7
|
+
return where;
|
|
8
|
+
}
|
|
9
|
+
function ownRowWhere(scope, id) {
|
|
10
|
+
return { ...scope, id, deletedAt: null };
|
|
11
|
+
}
|
|
12
|
+
function flagData(config, value, now) {
|
|
13
|
+
const data = { [config.flag]: value };
|
|
14
|
+
if (now) data.updatedAt = now;
|
|
15
|
+
return data;
|
|
16
|
+
}
|
|
17
|
+
async function clearExclusiveFlag(em, config, scope, keepId) {
|
|
18
|
+
const where = flaggedRowsWhere(config, scope, keepId);
|
|
19
|
+
const cleared = await findWithDecryption(em, config.entity, where, void 0, {
|
|
20
|
+
tenantId: scope.tenantId ?? null,
|
|
21
|
+
organizationId: scope.organizationId ?? null
|
|
22
|
+
});
|
|
23
|
+
if (!cleared.length) return [];
|
|
24
|
+
await em.nativeUpdate(config.entity, where, flagData(config, false, /* @__PURE__ */ new Date()));
|
|
25
|
+
return cleared;
|
|
26
|
+
}
|
|
27
|
+
async function promoteExclusiveFlag(em, config, scope, id, now) {
|
|
28
|
+
for (let attempt = 1; ; attempt += 1) {
|
|
29
|
+
try {
|
|
30
|
+
const cleared = await em.transactional(async (tem) => {
|
|
31
|
+
const rows = await clearExclusiveFlag(tem, config, scope, id);
|
|
32
|
+
await tem.nativeUpdate(config.entity, ownRowWhere(scope, id), flagData(config, true, now));
|
|
33
|
+
return rows;
|
|
34
|
+
});
|
|
35
|
+
return { promoted: true, cleared };
|
|
36
|
+
} catch (err) {
|
|
37
|
+
if (!isUniqueViolation(err, config.constraint)) throw err;
|
|
38
|
+
if (attempt >= PROMOTION_ATTEMPTS) return { promoted: false, cleared: [] };
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
async function assignExclusiveFlag(em, config, scope, id, toConflict) {
|
|
43
|
+
try {
|
|
44
|
+
const cleared = await clearExclusiveFlag(em, config, scope, id);
|
|
45
|
+
await em.nativeUpdate(config.entity, ownRowWhere(scope, id), flagData(config, true));
|
|
46
|
+
return cleared;
|
|
47
|
+
} catch (err) {
|
|
48
|
+
if (isUniqueViolation(err, config.constraint)) throw toConflict();
|
|
49
|
+
throw err;
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
export {
|
|
53
|
+
assignExclusiveFlag,
|
|
54
|
+
clearExclusiveFlag,
|
|
55
|
+
promoteExclusiveFlag
|
|
56
|
+
};
|
|
57
|
+
//# sourceMappingURL=exclusiveFlag.js.map
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": 3,
|
|
3
|
+
"sources": ["../../../../src/modules/ecommerce/lib/exclusiveFlag.ts"],
|
|
4
|
+
"sourcesContent": ["import type { EntityData, EntityManager, EntityName, FilterQuery } from '@mikro-orm/postgresql'\nimport { isUniqueViolation } from '@open-mercato/shared/lib/crud/errors'\nimport { findWithDecryption } from '@open-mercato/shared/lib/encryption/find'\n\nexport type ExclusiveFlagRow = { id: string }\n\nexport type ExclusiveFlagConfig<T extends ExclusiveFlagRow> = {\n entity: EntityName<T>\n flag: 'isPrimary' | 'isDefault'\n constraint: string\n}\n\nexport type ExclusiveFlagScope = Record<string, string>\n\nconst PROMOTION_ATTEMPTS = 3\n\nfunction flaggedRowsWhere<T extends ExclusiveFlagRow>(\n config: ExclusiveFlagConfig<T>,\n scope: ExclusiveFlagScope,\n keepId: string | null,\n): FilterQuery<T> {\n const where: Record<string, unknown> = { ...scope, [config.flag]: true, deletedAt: null }\n if (keepId) where.id = { $ne: keepId }\n return where as FilterQuery<T>\n}\n\nfunction ownRowWhere<T extends ExclusiveFlagRow>(scope: ExclusiveFlagScope, id: string): FilterQuery<T> {\n return { ...scope, id, deletedAt: null } as FilterQuery<T>\n}\n\nfunction flagData<T extends ExclusiveFlagRow>(config: ExclusiveFlagConfig<T>, value: boolean, now?: Date): EntityData<T> {\n const data: Record<string, unknown> = { [config.flag]: value }\n if (now) data.updatedAt = now\n return data as EntityData<T>\n}\n\nexport async function clearExclusiveFlag<T extends ExclusiveFlagRow>(\n em: EntityManager,\n config: ExclusiveFlagConfig<T>,\n scope: ExclusiveFlagScope,\n keepId: string | null,\n): Promise<T[]> {\n const where = flaggedRowsWhere(config, scope, keepId)\n const cleared = await findWithDecryption(em, config.entity, where, undefined, {\n tenantId: scope.tenantId ?? null,\n organizationId: scope.organizationId ?? null,\n })\n if (!cleared.length) return []\n await em.nativeUpdate(config.entity, where, flagData(config, false, new Date()))\n return cleared\n}\n\nexport async function promoteExclusiveFlag<T extends ExclusiveFlagRow>(\n em: EntityManager,\n config: ExclusiveFlagConfig<T>,\n scope: ExclusiveFlagScope,\n id: string,\n now: Date,\n): Promise<{ promoted: boolean; cleared: T[] }> {\n for (let attempt = 1; ; attempt += 1) {\n try {\n const cleared = await em.transactional(async (tem) => {\n const rows = await clearExclusiveFlag(tem, config, scope, id)\n await tem.nativeUpdate(config.entity, ownRowWhere<T>(scope, id), flagData(config, true, now))\n return rows\n })\n return { promoted: true, cleared }\n } catch (err) {\n if (!isUniqueViolation(err, config.constraint)) throw err\n if (attempt >= PROMOTION_ATTEMPTS) return { promoted: false, cleared: [] }\n }\n }\n}\n\nexport async function assignExclusiveFlag<T extends ExclusiveFlagRow>(\n em: EntityManager,\n config: ExclusiveFlagConfig<T>,\n scope: ExclusiveFlagScope,\n id: string,\n toConflict: () => unknown,\n): Promise<T[]> {\n try {\n const cleared = await clearExclusiveFlag(em, config, scope, id)\n await em.nativeUpdate(config.entity, ownRowWhere<T>(scope, id), flagData(config, true))\n return cleared\n } catch (err) {\n if (isUniqueViolation(err, config.constraint)) throw toConflict()\n throw err\n }\n}\n"],
|
|
5
|
+
"mappings": "AACA,SAAS,yBAAyB;AAClC,SAAS,0BAA0B;AAYnC,MAAM,qBAAqB;AAE3B,SAAS,iBACP,QACA,OACA,QACgB;AAChB,QAAM,QAAiC,EAAE,GAAG,OAAO,CAAC,OAAO,IAAI,GAAG,MAAM,WAAW,KAAK;AACxF,MAAI,OAAQ,OAAM,KAAK,EAAE,KAAK,OAAO;AACrC,SAAO;AACT;AAEA,SAAS,YAAwC,OAA2B,IAA4B;AACtG,SAAO,EAAE,GAAG,OAAO,IAAI,WAAW,KAAK;AACzC;AAEA,SAAS,SAAqC,QAAgC,OAAgB,KAA2B;AACvH,QAAM,OAAgC,EAAE,CAAC,OAAO,IAAI,GAAG,MAAM;AAC7D,MAAI,IAAK,MAAK,YAAY;AAC1B,SAAO;AACT;AAEA,eAAsB,mBACpB,IACA,QACA,OACA,QACc;AACd,QAAM,QAAQ,iBAAiB,QAAQ,OAAO,MAAM;AACpD,QAAM,UAAU,MAAM,mBAAmB,IAAI,OAAO,QAAQ,OAAO,QAAW;AAAA,IAC5E,UAAU,MAAM,YAAY;AAAA,IAC5B,gBAAgB,MAAM,kBAAkB;AAAA,EAC1C,CAAC;AACD,MAAI,CAAC,QAAQ,OAAQ,QAAO,CAAC;AAC7B,QAAM,GAAG,aAAa,OAAO,QAAQ,OAAO,SAAS,QAAQ,OAAO,oBAAI,KAAK,CAAC,CAAC;AAC/E,SAAO;AACT;AAEA,eAAsB,qBACpB,IACA,QACA,OACA,IACA,KAC8C;AAC9C,WAAS,UAAU,KAAK,WAAW,GAAG;AACpC,QAAI;AACF,YAAM,UAAU,MAAM,GAAG,cAAc,OAAO,QAAQ;AACpD,cAAM,OAAO,MAAM,mBAAmB,KAAK,QAAQ,OAAO,EAAE;AAC5D,cAAM,IAAI,aAAa,OAAO,QAAQ,YAAe,OAAO,EAAE,GAAG,SAAS,QAAQ,MAAM,GAAG,CAAC;AAC5F,eAAO;AAAA,MACT,CAAC;AACD,aAAO,EAAE,UAAU,MAAM,QAAQ;AAAA,IACnC,SAAS,KAAK;AACZ,UAAI,CAAC,kBAAkB,KAAK,OAAO,UAAU,EAAG,OAAM;AACtD,UAAI,WAAW,mBAAoB,QAAO,EAAE,UAAU,OAAO,SAAS,CAAC,EAAE;AAAA,IAC3E;AAAA,EACF;AACF;AAEA,eAAsB,oBACpB,IACA,QACA,OACA,IACA,YACc;AACd,MAAI;AACF,UAAM,UAAU,MAAM,mBAAmB,IAAI,QAAQ,OAAO,EAAE;AAC9D,UAAM,GAAG,aAAa,OAAO,QAAQ,YAAe,OAAO,EAAE,GAAG,SAAS,QAAQ,IAAI,CAAC;AACtF,WAAO;AAAA,EACT,SAAS,KAAK;AACZ,QAAI,kBAAkB,KAAK,OAAO,UAAU,EAAG,OAAM,WAAW;AAChE,UAAM;AAAA,EACR;AACF;",
|
|
6
|
+
"names": []
|
|
7
|
+
}
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
import { findOneWithDecryption } from "@open-mercato/shared/lib/encryption/find";
|
|
2
|
+
import { CatalogPriceKind } from "@open-mercato/core/modules/catalog/data/entities";
|
|
3
|
+
import { SalesChannel } from "@open-mercato/core/modules/sales/data/entities";
|
|
4
|
+
import { EcommerceStore } from "../data/entities.js";
|
|
5
|
+
import { fieldError } from "./crudSupport.js";
|
|
6
|
+
async function assertStoreInScope(em, storeId, scope, translate) {
|
|
7
|
+
const store = await findOneWithDecryption(
|
|
8
|
+
em,
|
|
9
|
+
EcommerceStore,
|
|
10
|
+
{ id: storeId, tenantId: scope.tenantId, organizationId: scope.organizationId, deletedAt: null },
|
|
11
|
+
void 0,
|
|
12
|
+
scope
|
|
13
|
+
);
|
|
14
|
+
if (store) return;
|
|
15
|
+
throw fieldError(400, {
|
|
16
|
+
storeId: translate("ecommerce.errors.storeNotFound", "The selected store does not exist in this organization.")
|
|
17
|
+
});
|
|
18
|
+
}
|
|
19
|
+
async function assertDomainMappingInScope(container, domainMappingId, scope, translate) {
|
|
20
|
+
const service = container.resolve("domainMappingService");
|
|
21
|
+
const mapping = await service.findById(domainMappingId, { tenantId: scope.tenantId });
|
|
22
|
+
if (mapping && mapping.tenantId === scope.tenantId && mapping.organizationId === scope.organizationId) return;
|
|
23
|
+
throw fieldError(400, {
|
|
24
|
+
domainMappingId: translate(
|
|
25
|
+
"ecommerce.errors.domainMappingNotFound",
|
|
26
|
+
"The selected domain does not belong to this organization."
|
|
27
|
+
)
|
|
28
|
+
});
|
|
29
|
+
}
|
|
30
|
+
async function assertSalesChannelInScope(em, salesChannelId, scope, translate) {
|
|
31
|
+
const channel = await findOneWithDecryption(
|
|
32
|
+
em,
|
|
33
|
+
SalesChannel,
|
|
34
|
+
{ id: salesChannelId, tenantId: scope.tenantId, organizationId: scope.organizationId, deletedAt: null },
|
|
35
|
+
void 0,
|
|
36
|
+
scope
|
|
37
|
+
);
|
|
38
|
+
if (channel) return;
|
|
39
|
+
throw fieldError(400, {
|
|
40
|
+
salesChannelId: translate(
|
|
41
|
+
"ecommerce.errors.salesChannelNotFound",
|
|
42
|
+
"The selected sales channel does not exist in this organization."
|
|
43
|
+
)
|
|
44
|
+
});
|
|
45
|
+
}
|
|
46
|
+
async function assertPriceKindInScope(em, priceKindId, scope, translate) {
|
|
47
|
+
const priceKind = await findOneWithDecryption(
|
|
48
|
+
em,
|
|
49
|
+
CatalogPriceKind,
|
|
50
|
+
{
|
|
51
|
+
id: priceKindId,
|
|
52
|
+
tenantId: scope.tenantId,
|
|
53
|
+
deletedAt: null,
|
|
54
|
+
$or: [{ organizationId: null }, { organizationId: scope.organizationId }]
|
|
55
|
+
},
|
|
56
|
+
void 0,
|
|
57
|
+
scope
|
|
58
|
+
);
|
|
59
|
+
if (priceKind) return;
|
|
60
|
+
throw fieldError(400, {
|
|
61
|
+
priceKindId: translate("ecommerce.errors.priceKindNotFound", "The selected price kind does not exist in this organization.")
|
|
62
|
+
});
|
|
63
|
+
}
|
|
64
|
+
export {
|
|
65
|
+
assertDomainMappingInScope,
|
|
66
|
+
assertPriceKindInScope,
|
|
67
|
+
assertSalesChannelInScope,
|
|
68
|
+
assertStoreInScope
|
|
69
|
+
};
|
|
70
|
+
//# sourceMappingURL=references.js.map
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": 3,
|
|
3
|
+
"sources": ["../../../../src/modules/ecommerce/lib/references.ts"],
|
|
4
|
+
"sourcesContent": ["import type { EntityManager } from '@mikro-orm/postgresql'\nimport type { CrudCtx } from '@open-mercato/shared/lib/crud/factory'\nimport { findOneWithDecryption } from '@open-mercato/shared/lib/encryption/find'\nimport { CatalogPriceKind } from '@open-mercato/core/modules/catalog/data/entities'\nimport { SalesChannel } from '@open-mercato/core/modules/sales/data/entities'\nimport { EcommerceStore } from '../data/entities'\nimport { fieldError, type EcommerceWriteScope, type Translate } from './crudSupport'\n\ntype DomainMappingReader = {\n findById(id: string, scope?: { tenantId?: string }): Promise<{ tenantId: string; organizationId: string } | null>\n}\n\nexport async function assertStoreInScope(\n em: EntityManager,\n storeId: string,\n scope: EcommerceWriteScope,\n translate: Translate,\n): Promise<void> {\n const store = await findOneWithDecryption(\n em,\n EcommerceStore,\n { id: storeId, tenantId: scope.tenantId, organizationId: scope.organizationId, deletedAt: null },\n undefined,\n scope,\n )\n if (store) return\n throw fieldError(400, {\n storeId: translate('ecommerce.errors.storeNotFound', 'The selected store does not exist in this organization.'),\n })\n}\n\nexport async function assertDomainMappingInScope(\n container: CrudCtx['container'],\n domainMappingId: string,\n scope: EcommerceWriteScope,\n translate: Translate,\n): Promise<void> {\n const service = container.resolve<DomainMappingReader>('domainMappingService')\n const mapping = await service.findById(domainMappingId, { tenantId: scope.tenantId })\n if (mapping && mapping.tenantId === scope.tenantId && mapping.organizationId === scope.organizationId) return\n throw fieldError(400, {\n domainMappingId: translate(\n 'ecommerce.errors.domainMappingNotFound',\n 'The selected domain does not belong to this organization.',\n ),\n })\n}\n\nexport async function assertSalesChannelInScope(\n em: EntityManager,\n salesChannelId: string,\n scope: EcommerceWriteScope,\n translate: Translate,\n): Promise<void> {\n const channel = await findOneWithDecryption(\n em,\n SalesChannel,\n { id: salesChannelId, tenantId: scope.tenantId, organizationId: scope.organizationId, deletedAt: null },\n undefined,\n scope,\n )\n if (channel) return\n throw fieldError(400, {\n salesChannelId: translate(\n 'ecommerce.errors.salesChannelNotFound',\n 'The selected sales channel does not exist in this organization.',\n ),\n })\n}\n\nexport async function assertPriceKindInScope(\n em: EntityManager,\n priceKindId: string,\n scope: EcommerceWriteScope,\n translate: Translate,\n): Promise<void> {\n const priceKind = await findOneWithDecryption(\n em,\n CatalogPriceKind,\n {\n id: priceKindId,\n tenantId: scope.tenantId,\n deletedAt: null,\n $or: [{ organizationId: null }, { organizationId: scope.organizationId }],\n },\n undefined,\n scope,\n )\n if (priceKind) return\n throw fieldError(400, {\n priceKindId: translate('ecommerce.errors.priceKindNotFound', 'The selected price kind does not exist in this organization.'),\n })\n}\n"],
|
|
5
|
+
"mappings": "AAEA,SAAS,6BAA6B;AACtC,SAAS,wBAAwB;AACjC,SAAS,oBAAoB;AAC7B,SAAS,sBAAsB;AAC/B,SAAS,kBAA4D;AAMrE,eAAsB,mBACpB,IACA,SACA,OACA,WACe;AACf,QAAM,QAAQ,MAAM;AAAA,IAClB;AAAA,IACA;AAAA,IACA,EAAE,IAAI,SAAS,UAAU,MAAM,UAAU,gBAAgB,MAAM,gBAAgB,WAAW,KAAK;AAAA,IAC/F;AAAA,IACA;AAAA,EACF;AACA,MAAI,MAAO;AACX,QAAM,WAAW,KAAK;AAAA,IACpB,SAAS,UAAU,kCAAkC,yDAAyD;AAAA,EAChH,CAAC;AACH;AAEA,eAAsB,2BACpB,WACA,iBACA,OACA,WACe;AACf,QAAM,UAAU,UAAU,QAA6B,sBAAsB;AAC7E,QAAM,UAAU,MAAM,QAAQ,SAAS,iBAAiB,EAAE,UAAU,MAAM,SAAS,CAAC;AACpF,MAAI,WAAW,QAAQ,aAAa,MAAM,YAAY,QAAQ,mBAAmB,MAAM,eAAgB;AACvG,QAAM,WAAW,KAAK;AAAA,IACpB,iBAAiB;AAAA,MACf;AAAA,MACA;AAAA,IACF;AAAA,EACF,CAAC;AACH;AAEA,eAAsB,0BACpB,IACA,gBACA,OACA,WACe;AACf,QAAM,UAAU,MAAM;AAAA,IACpB;AAAA,IACA;AAAA,IACA,EAAE,IAAI,gBAAgB,UAAU,MAAM,UAAU,gBAAgB,MAAM,gBAAgB,WAAW,KAAK;AAAA,IACtG;AAAA,IACA;AAAA,EACF;AACA,MAAI,QAAS;AACb,QAAM,WAAW,KAAK;AAAA,IACpB,gBAAgB;AAAA,MACd;AAAA,MACA;AAAA,IACF;AAAA,EACF,CAAC;AACH;AAEA,eAAsB,uBACpB,IACA,aACA,OACA,WACe;AACf,QAAM,YAAY,MAAM;AAAA,IACtB;AAAA,IACA;AAAA,IACA;AAAA,MACE,IAAI;AAAA,MACJ,UAAU,MAAM;AAAA,MAChB,WAAW;AAAA,MACX,KAAK,CAAC,EAAE,gBAAgB,KAAK,GAAG,EAAE,gBAAgB,MAAM,eAAe,CAAC;AAAA,IAC1E;AAAA,IACA;AAAA,IACA;AAAA,EACF;AACA,MAAI,UAAW;AACf,QAAM,WAAW,KAAK;AAAA,IACpB,aAAa,UAAU,sCAAsC,8DAA8D;AAAA,EAC7H,CAAC;AACH;",
|
|
6
|
+
"names": []
|
|
7
|
+
}
|
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
import { isUniqueViolation } from "@open-mercato/shared/lib/crud/errors";
|
|
2
|
+
import { findOneWithDecryption } from "@open-mercato/shared/lib/encryption/find";
|
|
3
|
+
import { defaultLocale } from "@open-mercato/shared/lib/i18n/config";
|
|
4
|
+
import { createLogger } from "@open-mercato/shared/lib/logger";
|
|
5
|
+
import { slugify } from "@open-mercato/shared/lib/slugify";
|
|
6
|
+
import { getTelemetryRuntime } from "@open-mercato/shared/lib/telemetry/runtime";
|
|
7
|
+
import { Organization } from "@open-mercato/core/modules/directory/data/entities";
|
|
8
|
+
import { EcommerceStore } from "../data/entities.js";
|
|
9
|
+
import { ecommerceStoreCreateSchema } from "../data/validators.js";
|
|
10
|
+
import { buildStoreEventPayload } from "./crudEvents.js";
|
|
11
|
+
import { emitEcommerceEvent } from "../events.js";
|
|
12
|
+
const logger = createLogger("ecommerce").child({ component: "seed-draft-store" });
|
|
13
|
+
const DEFAULT_DRAFT_STORE_NAME = "Default store";
|
|
14
|
+
const DEFAULT_DRAFT_STORE_CODE = "default";
|
|
15
|
+
const DEFAULT_DRAFT_STORE_CURRENCY = "USD";
|
|
16
|
+
const CODE_MAX_LENGTH = 80;
|
|
17
|
+
function deriveDraftStoreIdentity(organizationName) {
|
|
18
|
+
const name = (organizationName ?? "").trim().slice(0, 200) || DEFAULT_DRAFT_STORE_NAME;
|
|
19
|
+
const slug = slugify(name).replace(/-{2,}/g, "-").slice(0, CODE_MAX_LENGTH).replace(/-+$/, "") || DEFAULT_DRAFT_STORE_CODE;
|
|
20
|
+
return { name, code: slug, slug };
|
|
21
|
+
}
|
|
22
|
+
async function tenantHasStore(em, tenantId) {
|
|
23
|
+
return await em.count(EcommerceStore, { tenantId }) > 0;
|
|
24
|
+
}
|
|
25
|
+
async function loadOrganizationName(em, scope) {
|
|
26
|
+
const organization = await findOneWithDecryption(
|
|
27
|
+
em,
|
|
28
|
+
Organization,
|
|
29
|
+
{ id: scope.organizationId, tenant: scope.tenantId },
|
|
30
|
+
void 0,
|
|
31
|
+
scope
|
|
32
|
+
);
|
|
33
|
+
return organization?.name ?? null;
|
|
34
|
+
}
|
|
35
|
+
async function announceCreated(store) {
|
|
36
|
+
try {
|
|
37
|
+
await emitEcommerceEvent("ecommerce.store.created", buildStoreEventPayload(store), {
|
|
38
|
+
persistent: true,
|
|
39
|
+
tenantId: store.tenantId,
|
|
40
|
+
organizationId: store.organizationId
|
|
41
|
+
});
|
|
42
|
+
} catch (err) {
|
|
43
|
+
logger.warn("draft store created but the created event could not be emitted", { storeId: store.id, err });
|
|
44
|
+
getTelemetryRuntime()?.reportError(err, { module: "ecommerce", code: "ecommerce.seed_draft_store_event_failed" });
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
async function seedDraftStore(em, scope, options = {}) {
|
|
48
|
+
if (await tenantHasStore(em, scope.tenantId)) return { status: "exists" };
|
|
49
|
+
const identity = deriveDraftStoreIdentity(await loadOrganizationName(em, scope));
|
|
50
|
+
const resolvedCurrency = options.resolveCurrencyCode ? await options.resolveCurrencyCode() : null;
|
|
51
|
+
const input = ecommerceStoreCreateSchema.parse({
|
|
52
|
+
organizationId: scope.organizationId,
|
|
53
|
+
tenantId: scope.tenantId,
|
|
54
|
+
code: identity.code,
|
|
55
|
+
name: identity.name,
|
|
56
|
+
slug: identity.slug,
|
|
57
|
+
status: "draft",
|
|
58
|
+
defaultLocale,
|
|
59
|
+
supportedLocales: [defaultLocale],
|
|
60
|
+
defaultCurrencyCode: resolvedCurrency ?? DEFAULT_DRAFT_STORE_CURRENCY,
|
|
61
|
+
isPrimary: false
|
|
62
|
+
});
|
|
63
|
+
let store;
|
|
64
|
+
try {
|
|
65
|
+
store = await em.transactional(async (tem) => {
|
|
66
|
+
const created = tem.create(EcommerceStore, {
|
|
67
|
+
organizationId: input.organizationId,
|
|
68
|
+
tenantId: input.tenantId,
|
|
69
|
+
code: input.code,
|
|
70
|
+
name: input.name,
|
|
71
|
+
slug: input.slug,
|
|
72
|
+
status: input.status,
|
|
73
|
+
defaultLocale: input.defaultLocale,
|
|
74
|
+
supportedLocales: input.supportedLocales,
|
|
75
|
+
defaultCurrencyCode: input.defaultCurrencyCode,
|
|
76
|
+
isPrimary: false,
|
|
77
|
+
settings: input.settings
|
|
78
|
+
});
|
|
79
|
+
tem.persist(created);
|
|
80
|
+
await tem.flush();
|
|
81
|
+
return created;
|
|
82
|
+
});
|
|
83
|
+
} catch (err) {
|
|
84
|
+
if (!isUniqueViolation(err)) throw err;
|
|
85
|
+
logger.info("draft store already seeded concurrently", { tenantId: scope.tenantId, organizationId: scope.organizationId });
|
|
86
|
+
return { status: "exists" };
|
|
87
|
+
}
|
|
88
|
+
await announceCreated(store);
|
|
89
|
+
logger.info("draft store seeded", { tenantId: scope.tenantId, organizationId: scope.organizationId, storeId: store.id });
|
|
90
|
+
return { status: "created", storeId: store.id };
|
|
91
|
+
}
|
|
92
|
+
export {
|
|
93
|
+
DEFAULT_DRAFT_STORE_CODE,
|
|
94
|
+
DEFAULT_DRAFT_STORE_CURRENCY,
|
|
95
|
+
DEFAULT_DRAFT_STORE_NAME,
|
|
96
|
+
deriveDraftStoreIdentity,
|
|
97
|
+
seedDraftStore
|
|
98
|
+
};
|
|
99
|
+
//# sourceMappingURL=seedDraftStore.js.map
|