@open-mercato/core 0.9.1-develop.7473.1.de5b22888f → 0.9.1-develop.7475.1.28ca93ed97
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.turbo/turbo-build.log +2 -2
- package/dist/generated/entities/catalog_price_history_entry/index.js +53 -0
- package/dist/generated/entities/catalog_price_history_entry/index.js.map +7 -0
- package/dist/generated/entities/customer_group_terms/index.js +2 -0
- package/dist/generated/entities/customer_group_terms/index.js.map +2 -2
- package/dist/generated/entities/ecommerce_store/index.js +33 -0
- package/dist/generated/entities/ecommerce_store/index.js.map +7 -0
- package/dist/generated/entities/ecommerce_store_channel_binding/index.js +29 -0
- package/dist/generated/entities/ecommerce_store_channel_binding/index.js.map +7 -0
- package/dist/generated/entities/ecommerce_store_domain_binding/index.js +23 -0
- package/dist/generated/entities/ecommerce_store_domain_binding/index.js.map +7 -0
- package/dist/generated/entities.ids.generated.js +8 -1
- package/dist/generated/entities.ids.generated.js.map +2 -2
- package/dist/generated/entity-fields-registry.js +72 -0
- package/dist/generated/entity-fields-registry.js.map +2 -2
- package/dist/helpers/integration/customerGroupsFixtures.js.map +2 -2
- package/dist/modules/catalog/acl.js +8 -1
- package/dist/modules/catalog/acl.js.map +2 -2
- package/dist/modules/catalog/api/config/omnibus/route.js +184 -0
- package/dist/modules/catalog/api/config/omnibus/route.js.map +7 -0
- package/dist/modules/catalog/api/prices/history/route.js +131 -0
- package/dist/modules/catalog/api/prices/history/route.js.map +7 -0
- package/dist/modules/catalog/api/prices/omnibus-preview/route.js +184 -0
- package/dist/modules/catalog/api/prices/omnibus-preview/route.js.map +7 -0
- package/dist/modules/catalog/api/products/route.js +54 -245
- package/dist/modules/catalog/api/products/route.js.map +3 -3
- package/dist/modules/catalog/api/tags/route.js +6 -0
- package/dist/modules/catalog/api/tags/route.js.map +2 -2
- package/dist/modules/catalog/backend/config/catalog/page.js +3 -1
- package/dist/modules/catalog/backend/config/catalog/page.js.map +2 -2
- package/dist/modules/catalog/cli.js +80 -2
- package/dist/modules/catalog/cli.js.map +2 -2
- package/dist/modules/catalog/commands/categories.js +51 -2
- package/dist/modules/catalog/commands/categories.js.map +2 -2
- package/dist/modules/catalog/commands/prices.js +43 -1
- package/dist/modules/catalog/commands/prices.js.map +2 -2
- package/dist/modules/catalog/commands/products.js +71 -0
- package/dist/modules/catalog/commands/products.js.map +2 -2
- package/dist/modules/catalog/commands/variants.js +7 -0
- package/dist/modules/catalog/commands/variants.js.map +2 -2
- package/dist/modules/catalog/components/OmnibusSettings.js +703 -0
- package/dist/modules/catalog/components/OmnibusSettings.js.map +7 -0
- package/dist/modules/catalog/components/PriceEditorOmnibusRow.js +152 -0
- package/dist/modules/catalog/components/PriceEditorOmnibusRow.js.map +7 -0
- package/dist/modules/catalog/components/lookupSources.js +31 -0
- package/dist/modules/catalog/components/lookupSources.js.map +7 -0
- package/dist/modules/catalog/components/prices/PriceScopeSelectors.js +8 -2
- package/dist/modules/catalog/components/prices/PriceScopeSelectors.js.map +2 -2
- package/dist/modules/catalog/components/products/VariantBuilder.js +11 -1
- package/dist/modules/catalog/components/products/VariantBuilder.js.map +2 -2
- package/dist/modules/catalog/data/entities.js +122 -1
- package/dist/modules/catalog/data/entities.js.map +2 -2
- package/dist/modules/catalog/data/validators.js +78 -0
- package/dist/modules/catalog/data/validators.js.map +2 -2
- package/dist/modules/catalog/di.js +18 -0
- package/dist/modules/catalog/di.js.map +2 -2
- package/dist/modules/catalog/lib/omnibus.js +285 -0
- package/dist/modules/catalog/lib/omnibus.js.map +7 -0
- package/dist/modules/catalog/lib/omnibusBackfill.js +146 -0
- package/dist/modules/catalog/lib/omnibusBackfill.js.map +7 -0
- package/dist/modules/catalog/lib/omnibusCache.js +89 -0
- package/dist/modules/catalog/lib/omnibusCache.js.map +7 -0
- package/dist/modules/catalog/lib/omnibusConfig.js +64 -0
- package/dist/modules/catalog/lib/omnibusConfig.js.map +7 -0
- package/dist/modules/catalog/lib/omnibusHistoryQueries.js +191 -0
- package/dist/modules/catalog/lib/omnibusHistoryQueries.js.map +7 -0
- package/dist/modules/catalog/lib/omnibusPresentedEntry.js +73 -0
- package/dist/modules/catalog/lib/omnibusPresentedEntry.js.map +7 -0
- package/dist/modules/catalog/lib/omnibusProductListEnrichment.js +94 -0
- package/dist/modules/catalog/lib/omnibusProductListEnrichment.js.map +7 -0
- package/dist/modules/catalog/lib/omnibusTypes.js +115 -0
- package/dist/modules/catalog/lib/omnibusTypes.js.map +7 -0
- package/dist/modules/catalog/lib/optionSchemaTranslations.js +55 -0
- package/dist/modules/catalog/lib/optionSchemaTranslations.js.map +7 -0
- package/dist/modules/catalog/lib/priceHistoryQuery.js +103 -0
- package/dist/modules/catalog/lib/priceHistoryQuery.js.map +7 -0
- package/dist/modules/catalog/lib/pricing.js +26 -2
- package/dist/modules/catalog/lib/pricing.js.map +2 -2
- package/dist/modules/catalog/lib/productFilters.js +273 -0
- package/dist/modules/catalog/lib/productFilters.js.map +7 -0
- package/dist/modules/catalog/lib/productScopeKeys.js +105 -0
- package/dist/modules/catalog/lib/productScopeKeys.js.map +7 -0
- package/dist/modules/catalog/lib/productScopeReindex.js +73 -0
- package/dist/modules/catalog/lib/productScopeReindex.js.map +7 -0
- package/dist/modules/catalog/migrations/Migration20261005105352_catalog.js +39 -0
- package/dist/modules/catalog/migrations/Migration20261005105352_catalog.js.map +7 -0
- package/dist/modules/catalog/migrations/Migration20261005150824_catalog.js +39 -0
- package/dist/modules/catalog/migrations/Migration20261005150824_catalog.js.map +7 -0
- package/dist/modules/catalog/services/catalogOmnibusService.js +480 -0
- package/dist/modules/catalog/services/catalogOmnibusService.js.map +7 -0
- package/dist/modules/catalog/setup.js +3 -1
- package/dist/modules/catalog/setup.js.map +2 -2
- package/dist/modules/catalog/subscribers/product-scope-keys-category-deleted.js +16 -0
- package/dist/modules/catalog/subscribers/product-scope-keys-category-deleted.js.map +7 -0
- package/dist/modules/catalog/subscribers/product-scope-keys-category-updated.js +17 -0
- package/dist/modules/catalog/subscribers/product-scope-keys-category-updated.js.map +7 -0
- package/dist/modules/catalog/translations.js +10 -0
- package/dist/modules/catalog/translations.js.map +2 -2
- package/dist/modules/configs/lib/upgrade-actions.js +28 -0
- package/dist/modules/configs/lib/upgrade-actions.js.map +2 -2
- package/dist/modules/customer_groups/api/customer-groups/[id]/terms/route.js +5 -0
- package/dist/modules/customer_groups/api/customer-groups/[id]/terms/route.js.map +2 -2
- package/dist/modules/customer_groups/components/CustomerGroupTermsSection.js +111 -3
- package/dist/modules/customer_groups/components/CustomerGroupTermsSection.js.map +2 -2
- package/dist/modules/customer_groups/components/customerGroupAssortmentScope.js +43 -0
- package/dist/modules/customer_groups/components/customerGroupAssortmentScope.js.map +7 -0
- package/dist/modules/customer_groups/data/entities.js +3 -0
- package/dist/modules/customer_groups/data/entities.js.map +2 -2
- package/dist/modules/customer_groups/data/validators.js +9 -0
- package/dist/modules/customer_groups/data/validators.js.map +2 -2
- package/dist/modules/customer_groups/migrations/Migration20261005110250_customer_groups.js +17 -0
- package/dist/modules/customer_groups/migrations/Migration20261005110250_customer_groups.js.map +7 -0
- package/dist/modules/customer_groups/services/customerGroupsService.js +62 -15
- package/dist/modules/customer_groups/services/customerGroupsService.js.map +2 -2
- package/dist/modules/ecommerce/acl.js +13 -0
- package/dist/modules/ecommerce/acl.js.map +7 -0
- package/dist/modules/ecommerce/api/domain-mappings/route.js +72 -0
- package/dist/modules/ecommerce/api/domain-mappings/route.js.map +7 -0
- package/dist/modules/ecommerce/api/openapi.js +27 -0
- package/dist/modules/ecommerce/api/openapi.js.map +7 -0
- package/dist/modules/ecommerce/api/store-channel-bindings/[id]/assortment-count/route.js +109 -0
- package/dist/modules/ecommerce/api/store-channel-bindings/[id]/assortment-count/route.js.map +7 -0
- package/dist/modules/ecommerce/api/store-channel-bindings/crud.js +265 -0
- package/dist/modules/ecommerce/api/store-channel-bindings/crud.js.map +7 -0
- package/dist/modules/ecommerce/api/store-channel-bindings/route.js +67 -0
- package/dist/modules/ecommerce/api/store-channel-bindings/route.js.map +7 -0
- package/dist/modules/ecommerce/api/store-domain-bindings/crud.js +296 -0
- package/dist/modules/ecommerce/api/store-domain-bindings/crud.js.map +7 -0
- package/dist/modules/ecommerce/api/store-domain-bindings/route.js +73 -0
- package/dist/modules/ecommerce/api/store-domain-bindings/route.js.map +7 -0
- package/dist/modules/ecommerce/api/storefront/categories/[slug]/route.js +87 -0
- package/dist/modules/ecommerce/api/storefront/categories/[slug]/route.js.map +7 -0
- package/dist/modules/ecommerce/api/storefront/categories/openapiSchemas.js +50 -0
- package/dist/modules/ecommerce/api/storefront/categories/openapiSchemas.js.map +7 -0
- package/dist/modules/ecommerce/api/storefront/categories/route.js +75 -0
- package/dist/modules/ecommerce/api/storefront/categories/route.js.map +7 -0
- package/dist/modules/ecommerce/api/storefront/context/route.js +163 -0
- package/dist/modules/ecommerce/api/storefront/context/route.js.map +7 -0
- package/dist/modules/ecommerce/api/storefront/products/[idOrHandle]/route.js +87 -0
- package/dist/modules/ecommerce/api/storefront/products/[idOrHandle]/route.js.map +7 -0
- package/dist/modules/ecommerce/api/storefront/products/openapiSchemas.js +177 -0
- package/dist/modules/ecommerce/api/storefront/products/openapiSchemas.js.map +7 -0
- package/dist/modules/ecommerce/api/storefront/products/route.js +78 -0
- package/dist/modules/ecommerce/api/storefront/products/route.js.map +7 -0
- package/dist/modules/ecommerce/api/storefront/search/suggest/openapiSchemas.js +25 -0
- package/dist/modules/ecommerce/api/storefront/search/suggest/openapiSchemas.js.map +7 -0
- package/dist/modules/ecommerce/api/storefront/search/suggest/route.js +75 -0
- package/dist/modules/ecommerce/api/storefront/search/suggest/route.js.map +7 -0
- package/dist/modules/ecommerce/api/storefront/storefrontRouteSupport.js +58 -0
- package/dist/modules/ecommerce/api/storefront/storefrontRouteSupport.js.map +7 -0
- package/dist/modules/ecommerce/api/stores/[id]/branding/route.js +183 -0
- package/dist/modules/ecommerce/api/stores/[id]/branding/route.js.map +7 -0
- package/dist/modules/ecommerce/api/stores/[id]/preview-branding/route.js +92 -0
- package/dist/modules/ecommerce/api/stores/[id]/preview-branding/route.js.map +7 -0
- package/dist/modules/ecommerce/api/stores/crud.js +401 -0
- package/dist/modules/ecommerce/api/stores/crud.js.map +7 -0
- package/dist/modules/ecommerce/api/stores/route.js +64 -0
- package/dist/modules/ecommerce/api/stores/route.js.map +7 -0
- package/dist/modules/ecommerce/backend/config/ecommerce/[id]/page.js +9 -0
- package/dist/modules/ecommerce/backend/config/ecommerce/[id]/page.js.map +7 -0
- package/dist/modules/ecommerce/backend/config/ecommerce/[id]/page.meta.js +16 -0
- package/dist/modules/ecommerce/backend/config/ecommerce/[id]/page.meta.js.map +7 -0
- package/dist/modules/ecommerce/backend/config/ecommerce/page.js +9 -0
- package/dist/modules/ecommerce/backend/config/ecommerce/page.js.map +7 -0
- package/dist/modules/ecommerce/backend/config/ecommerce/page.meta.js +16 -0
- package/dist/modules/ecommerce/backend/config/ecommerce/page.meta.js.map +7 -0
- package/dist/modules/ecommerce/commands/stores.js +110 -0
- package/dist/modules/ecommerce/commands/stores.js.map +7 -0
- package/dist/modules/ecommerce/components/ChannelAssortmentCount.js +90 -0
- package/dist/modules/ecommerce/components/ChannelAssortmentCount.js.map +7 -0
- package/dist/modules/ecommerce/components/ChannelBindingFormSections.js +33 -0
- package/dist/modules/ecommerce/components/ChannelBindingFormSections.js.map +7 -0
- package/dist/modules/ecommerce/components/ChannelLookupPicker.js +11 -0
- package/dist/modules/ecommerce/components/ChannelLookupPicker.js.map +7 -0
- package/dist/modules/ecommerce/components/DomainStatusWarning.js +24 -0
- package/dist/modules/ecommerce/components/DomainStatusWarning.js.map +7 -0
- package/dist/modules/ecommerce/components/StoreAvailabilityDefaultsSection.js +190 -0
- package/dist/modules/ecommerce/components/StoreAvailabilityDefaultsSection.js.map +7 -0
- package/dist/modules/ecommerce/components/StoreBrandingControls.js +67 -0
- package/dist/modules/ecommerce/components/StoreBrandingControls.js.map +7 -0
- package/dist/modules/ecommerce/components/StoreBrandingPreview.js +69 -0
- package/dist/modules/ecommerce/components/StoreBrandingPreview.js.map +7 -0
- package/dist/modules/ecommerce/components/StoreBrandingTab.js +218 -0
- package/dist/modules/ecommerce/components/StoreBrandingTab.js.map +7 -0
- package/dist/modules/ecommerce/components/StoreChannelBindingDialog.js +270 -0
- package/dist/modules/ecommerce/components/StoreChannelBindingDialog.js.map +7 -0
- package/dist/modules/ecommerce/components/StoreChannelsTab.js +313 -0
- package/dist/modules/ecommerce/components/StoreChannelsTab.js.map +7 -0
- package/dist/modules/ecommerce/components/StoreCreateDialog.js +164 -0
- package/dist/modules/ecommerce/components/StoreCreateDialog.js.map +7 -0
- package/dist/modules/ecommerce/components/StoreDomainBindingDialog.js +162 -0
- package/dist/modules/ecommerce/components/StoreDomainBindingDialog.js.map +7 -0
- package/dist/modules/ecommerce/components/StoreDomainsTab.js +357 -0
- package/dist/modules/ecommerce/components/StoreDomainsTab.js.map +7 -0
- package/dist/modules/ecommerce/components/StoreEditPage.js +155 -0
- package/dist/modules/ecommerce/components/StoreEditPage.js.map +7 -0
- package/dist/modules/ecommerce/components/StoreGeneralTab.js +167 -0
- package/dist/modules/ecommerce/components/StoreGeneralTab.js.map +7 -0
- package/dist/modules/ecommerce/components/StoreListPage.js +290 -0
- package/dist/modules/ecommerce/components/StoreListPage.js.map +7 -0
- package/dist/modules/ecommerce/components/StoreSeoTab.js +132 -0
- package/dist/modules/ecommerce/components/StoreSeoTab.js.map +7 -0
- package/dist/modules/ecommerce/components/StoreStatusBadge.js +20 -0
- package/dist/modules/ecommerce/components/StoreStatusBadge.js.map +7 -0
- package/dist/modules/ecommerce/components/channelLookups.js +28 -0
- package/dist/modules/ecommerce/components/channelLookups.js.map +7 -0
- package/dist/modules/ecommerce/components/storeAdmin.js +41 -0
- package/dist/modules/ecommerce/components/storeAdmin.js.map +7 -0
- package/dist/modules/ecommerce/components/storeBrandingForm.js +197 -0
- package/dist/modules/ecommerce/components/storeBrandingForm.js.map +7 -0
- package/dist/modules/ecommerce/components/storeChannels.js +140 -0
- package/dist/modules/ecommerce/components/storeChannels.js.map +7 -0
- package/dist/modules/ecommerce/components/storeDomains.js +167 -0
- package/dist/modules/ecommerce/components/storeDomains.js.map +7 -0
- package/dist/modules/ecommerce/components/storeEditTabs.js +62 -0
- package/dist/modules/ecommerce/components/storeEditTabs.js.map +7 -0
- package/dist/modules/ecommerce/components/storeGeneral.js +95 -0
- package/dist/modules/ecommerce/components/storeGeneral.js.map +7 -0
- package/dist/modules/ecommerce/components/storeSeo.js +69 -0
- package/dist/modules/ecommerce/components/storeSeo.js.map +7 -0
- package/dist/modules/ecommerce/components/useChannelAssortmentCount.js +45 -0
- package/dist/modules/ecommerce/components/useChannelAssortmentCount.js.map +7 -0
- package/dist/modules/ecommerce/components/useStoreAccess.js +28 -0
- package/dist/modules/ecommerce/components/useStoreAccess.js.map +7 -0
- package/dist/modules/ecommerce/data/enrichers.js +157 -0
- package/dist/modules/ecommerce/data/enrichers.js.map +7 -0
- package/dist/modules/ecommerce/data/entities.js +197 -0
- package/dist/modules/ecommerce/data/entities.js.map +7 -0
- package/dist/modules/ecommerce/data/validators.js +406 -0
- package/dist/modules/ecommerce/data/validators.js.map +7 -0
- package/dist/modules/ecommerce/di.js +11 -0
- package/dist/modules/ecommerce/di.js.map +7 -0
- package/dist/modules/ecommerce/events.js +27 -0
- package/dist/modules/ecommerce/events.js.map +7 -0
- package/dist/modules/ecommerce/index.js +16 -0
- package/dist/modules/ecommerce/index.js.map +7 -0
- package/dist/modules/ecommerce/lib/assortmentCount.js +112 -0
- package/dist/modules/ecommerce/lib/assortmentCount.js.map +7 -0
- package/dist/modules/ecommerce/lib/brandingStyles.js +184 -0
- package/dist/modules/ecommerce/lib/brandingStyles.js.map +7 -0
- package/dist/modules/ecommerce/lib/buyerContext.js +284 -0
- package/dist/modules/ecommerce/lib/buyerContext.js.map +7 -0
- package/dist/modules/ecommerce/lib/cacheInvalidation.js +186 -0
- package/dist/modules/ecommerce/lib/cacheInvalidation.js.map +7 -0
- package/dist/modules/ecommerce/lib/cacheKeys.js +236 -0
- package/dist/modules/ecommerce/lib/cacheKeys.js.map +7 -0
- package/dist/modules/ecommerce/lib/createConflictRecheck.js +29 -0
- package/dist/modules/ecommerce/lib/createConflictRecheck.js.map +7 -0
- package/dist/modules/ecommerce/lib/crudEvents.js +94 -0
- package/dist/modules/ecommerce/lib/crudEvents.js.map +7 -0
- package/dist/modules/ecommerce/lib/crudSupport.js +64 -0
- package/dist/modules/ecommerce/lib/crudSupport.js.map +7 -0
- package/dist/modules/ecommerce/lib/domainBindingRebind.js +99 -0
- package/dist/modules/ecommerce/lib/domainBindingRebind.js.map +7 -0
- package/dist/modules/ecommerce/lib/domainMappingSummaries.js +39 -0
- package/dist/modules/ecommerce/lib/domainMappingSummaries.js.map +7 -0
- package/dist/modules/ecommerce/lib/exclusiveFlag.js +57 -0
- package/dist/modules/ecommerce/lib/exclusiveFlag.js.map +7 -0
- package/dist/modules/ecommerce/lib/references.js +70 -0
- package/dist/modules/ecommerce/lib/references.js.map +7 -0
- package/dist/modules/ecommerce/lib/seedDraftStore.js +99 -0
- package/dist/modules/ecommerce/lib/seedDraftStore.js.map +7 -0
- package/dist/modules/ecommerce/lib/storeBindingCascade.js +109 -0
- package/dist/modules/ecommerce/lib/storeBindingCascade.js.map +7 -0
- package/dist/modules/ecommerce/lib/storeBranding.js +40 -0
- package/dist/modules/ecommerce/lib/storeBranding.js.map +7 -0
- package/dist/modules/ecommerce/lib/storeBrandingRoute.js +76 -0
- package/dist/modules/ecommerce/lib/storeBrandingRoute.js.map +7 -0
- package/dist/modules/ecommerce/lib/storeContext.js +393 -0
- package/dist/modules/ecommerce/lib/storeContext.js.map +7 -0
- package/dist/modules/ecommerce/lib/storeContextService.js +43 -0
- package/dist/modules/ecommerce/lib/storeContextService.js.map +7 -0
- package/dist/modules/ecommerce/lib/storeLocale.js +60 -0
- package/dist/modules/ecommerce/lib/storeLocale.js.map +7 -0
- package/dist/modules/ecommerce/lib/storeNotifications.js +41 -0
- package/dist/modules/ecommerce/lib/storeNotifications.js.map +7 -0
- package/dist/modules/ecommerce/lib/storefrontCatalogSupport.js +248 -0
- package/dist/modules/ecommerce/lib/storefrontCatalogSupport.js.map +7 -0
- package/dist/modules/ecommerce/lib/storefrontCategories.js +241 -0
- package/dist/modules/ecommerce/lib/storefrontCategories.js.map +7 -0
- package/dist/modules/ecommerce/lib/storefrontCategoryCache.js +71 -0
- package/dist/modules/ecommerce/lib/storefrontCategoryCache.js.map +7 -0
- package/dist/modules/ecommerce/lib/storefrontDetail.js +447 -0
- package/dist/modules/ecommerce/lib/storefrontDetail.js.map +7 -0
- package/dist/modules/ecommerce/lib/storefrontFacets.js +441 -0
- package/dist/modules/ecommerce/lib/storefrontFacets.js.map +7 -0
- package/dist/modules/ecommerce/lib/storefrontOptionFilter.js +15 -0
- package/dist/modules/ecommerce/lib/storefrontOptionFilter.js.map +7 -0
- package/dist/modules/ecommerce/lib/storefrontPricing.js +393 -0
- package/dist/modules/ecommerce/lib/storefrontPricing.js.map +7 -0
- package/dist/modules/ecommerce/lib/storefrontProductCache.js +90 -0
- package/dist/modules/ecommerce/lib/storefrontProductCache.js.map +7 -0
- package/dist/modules/ecommerce/lib/storefrontProductScope.js +107 -0
- package/dist/modules/ecommerce/lib/storefrontProductScope.js.map +7 -0
- package/dist/modules/ecommerce/lib/storefrontProducts.js +670 -0
- package/dist/modules/ecommerce/lib/storefrontProducts.js.map +7 -0
- package/dist/modules/ecommerce/lib/storefrontQuery.js +156 -0
- package/dist/modules/ecommerce/lib/storefrontQuery.js.map +7 -0
- package/dist/modules/ecommerce/lib/storefrontRateLimit.js +99 -0
- package/dist/modules/ecommerce/lib/storefrontRateLimit.js.map +7 -0
- package/dist/modules/ecommerce/lib/storefrontSearch.js +303 -0
- package/dist/modules/ecommerce/lib/storefrontSearch.js.map +7 -0
- package/dist/modules/ecommerce/lib/storefrontSearchCache.js +35 -0
- package/dist/modules/ecommerce/lib/storefrontSearchCache.js.map +7 -0
- package/dist/modules/ecommerce/lib/subscriberSupport.js +33 -0
- package/dist/modules/ecommerce/lib/subscriberSupport.js.map +7 -0
- package/dist/modules/ecommerce/lib/types.js +1 -0
- package/dist/modules/ecommerce/lib/types.js.map +7 -0
- package/dist/modules/ecommerce/migrations/Migration20261005113233_ecommerce.js +32 -0
- package/dist/modules/ecommerce/migrations/Migration20261005113233_ecommerce.js.map +7 -0
- package/dist/modules/ecommerce/migrations/Migration20261006112733_ecommerce.js +17 -0
- package/dist/modules/ecommerce/migrations/Migration20261006112733_ecommerce.js.map +7 -0
- package/dist/modules/ecommerce/notifications.client.js +47 -0
- package/dist/modules/ecommerce/notifications.client.js.map +7 -0
- package/dist/modules/ecommerce/notifications.js +48 -0
- package/dist/modules/ecommerce/notifications.js.map +7 -0
- package/dist/modules/ecommerce/search.js +83 -0
- package/dist/modules/ecommerce/search.js.map +7 -0
- package/dist/modules/ecommerce/setup.js +17 -0
- package/dist/modules/ecommerce/setup.js.map +7 -0
- package/dist/modules/ecommerce/subscribers/assortment-empty-notifications.js +14 -0
- package/dist/modules/ecommerce/subscribers/assortment-empty-notifications.js.map +7 -0
- package/dist/modules/ecommerce/subscribers/availability-policy-cache-invalidation.js +14 -0
- package/dist/modules/ecommerce/subscribers/availability-policy-cache-invalidation.js.map +7 -0
- package/dist/modules/ecommerce/subscribers/catalog-category-cache-invalidation.js +14 -0
- package/dist/modules/ecommerce/subscribers/catalog-category-cache-invalidation.js.map +7 -0
- package/dist/modules/ecommerce/subscribers/catalog-price-cache-invalidation.js +14 -0
- package/dist/modules/ecommerce/subscribers/catalog-price-cache-invalidation.js.map +7 -0
- package/dist/modules/ecommerce/subscribers/catalog-product-cache-invalidation.js +14 -0
- package/dist/modules/ecommerce/subscribers/catalog-product-cache-invalidation.js.map +7 -0
- package/dist/modules/ecommerce/subscribers/catalog-variant-cache-invalidation.js +14 -0
- package/dist/modules/ecommerce/subscribers/catalog-variant-cache-invalidation.js.map +7 -0
- package/dist/modules/ecommerce/subscribers/customer-group-cache-invalidation.js +14 -0
- package/dist/modules/ecommerce/subscribers/customer-group-cache-invalidation.js.map +7 -0
- package/dist/modules/ecommerce/subscribers/customer-group-membership-cache-invalidation.js +14 -0
- package/dist/modules/ecommerce/subscribers/customer-group-membership-cache-invalidation.js.map +7 -0
- package/dist/modules/ecommerce/subscribers/customer-group-terms-cache-invalidation.js +14 -0
- package/dist/modules/ecommerce/subscribers/customer-group-terms-cache-invalidation.js.map +7 -0
- package/dist/modules/ecommerce/subscribers/customer-user-deleted-cache-invalidation.js +14 -0
- package/dist/modules/ecommerce/subscribers/customer-user-deleted-cache-invalidation.js.map +7 -0
- package/dist/modules/ecommerce/subscribers/customer-user-updated-cache-invalidation.js +14 -0
- package/dist/modules/ecommerce/subscribers/customer-user-updated-cache-invalidation.js.map +7 -0
- package/dist/modules/ecommerce/subscribers/domain-binding-rebind.js +16 -0
- package/dist/modules/ecommerce/subscribers/domain-binding-rebind.js.map +7 -0
- package/dist/modules/ecommerce/subscribers/domain-mapping-cache-invalidation.js +14 -0
- package/dist/modules/ecommerce/subscribers/domain-mapping-cache-invalidation.js.map +7 -0
- package/dist/modules/ecommerce/subscribers/store-cache-invalidation.js +14 -0
- package/dist/modules/ecommerce/subscribers/store-cache-invalidation.js.map +7 -0
- package/dist/modules/ecommerce/subscribers/store-channel-binding-cache-invalidation.js +14 -0
- package/dist/modules/ecommerce/subscribers/store-channel-binding-cache-invalidation.js.map +7 -0
- package/dist/modules/ecommerce/subscribers/store-domain-binding-cache-invalidation.js +14 -0
- package/dist/modules/ecommerce/subscribers/store-domain-binding-cache-invalidation.js.map +7 -0
- package/dist/modules/ecommerce/subscribers/store-notifications.js +14 -0
- package/dist/modules/ecommerce/subscribers/store-notifications.js.map +7 -0
- package/dist/modules/query_index/lib/batch.js +23 -9
- package/dist/modules/query_index/lib/batch.js.map +2 -2
- package/dist/modules/query_index/lib/doc-enrichers.js +212 -0
- package/dist/modules/query_index/lib/doc-enrichers.js.map +7 -0
- package/dist/modules/query_index/lib/document.js +2 -0
- package/dist/modules/query_index/lib/document.js.map +2 -2
- package/dist/modules/query_index/lib/engine.js +68 -1
- package/dist/modules/query_index/lib/engine.js.map +2 -2
- package/dist/modules/query_index/lib/indexer.js +7 -0
- package/dist/modules/query_index/lib/indexer.js.map +2 -2
- package/dist/modules/query_index/lib/search-tokens.js +2 -0
- package/dist/modules/query_index/lib/search-tokens.js.map +2 -2
- package/dist/modules/query_index/migrations/Migration20261005201500_query_index.js +17 -0
- package/dist/modules/query_index/migrations/Migration20261005201500_query_index.js.map +7 -0
- package/dist/modules/translations/components/TranslationManager.js +3 -3
- package/dist/modules/translations/components/TranslationManager.js.map +2 -2
- package/dist/modules/translations/data/validators.js +1 -1
- package/dist/modules/translations/data/validators.js.map +1 -1
- package/dist/modules/translations/lib/batch.js +21 -1
- package/dist/modules/translations/lib/batch.js.map +2 -2
- package/dist/modules/translations/lib/helpers.js +3 -0
- package/dist/modules/translations/lib/helpers.js.map +2 -2
- package/dist/modules/translations/lib/resolve-field-list.js +14 -2
- package/dist/modules/translations/lib/resolve-field-list.js.map +2 -2
- package/generated/entities/catalog_price_history_entry/index.ts +25 -0
- package/generated/entities/customer_group_terms/index.ts +1 -0
- package/generated/entities/ecommerce_store/index.ts +15 -0
- package/generated/entities/ecommerce_store_channel_binding/index.ts +13 -0
- package/generated/entities/ecommerce_store_domain_binding/index.ts +10 -0
- package/generated/entities.ids.generated.ts +8 -1
- package/generated/entity-fields-registry.ts +72 -0
- package/package.json +7 -7
- package/src/helpers/integration/customerGroupsFixtures.ts +2 -0
- package/src/modules/auth/i18n/de.json +8 -0
- package/src/modules/auth/i18n/en.json +8 -0
- package/src/modules/auth/i18n/es.json +8 -0
- package/src/modules/auth/i18n/ko.json +8 -0
- package/src/modules/auth/i18n/pl.json +8 -0
- package/src/modules/catalog/AGENTS.md +5 -2
- package/src/modules/catalog/acl.ts +7 -0
- package/src/modules/catalog/api/config/omnibus/route.ts +204 -0
- package/src/modules/catalog/api/prices/history/route.ts +139 -0
- package/src/modules/catalog/api/prices/omnibus-preview/route.ts +202 -0
- package/src/modules/catalog/api/products/route.ts +53 -308
- package/src/modules/catalog/api/tags/route.ts +6 -0
- package/src/modules/catalog/backend/config/catalog/page.tsx +2 -0
- package/src/modules/catalog/cli.ts +84 -1
- package/src/modules/catalog/commands/categories.ts +54 -2
- package/src/modules/catalog/commands/prices.ts +43 -1
- package/src/modules/catalog/commands/products.ts +84 -1
- package/src/modules/catalog/commands/variants.ts +7 -0
- package/src/modules/catalog/components/OmnibusSettings.tsx +832 -0
- package/src/modules/catalog/components/PriceEditorOmnibusRow.tsx +219 -0
- package/src/modules/catalog/components/lookupSources.ts +29 -0
- package/src/modules/catalog/components/prices/PriceScopeSelectors.tsx +9 -0
- package/src/modules/catalog/components/products/VariantBuilder.tsx +9 -0
- package/src/modules/catalog/data/entities.ts +123 -1
- package/src/modules/catalog/data/validators.ts +118 -0
- package/src/modules/catalog/di.ts +23 -0
- package/src/modules/catalog/i18n/de.json +77 -0
- package/src/modules/catalog/i18n/en.json +77 -0
- package/src/modules/catalog/i18n/es.json +77 -0
- package/src/modules/catalog/i18n/ko.json +77 -0
- package/src/modules/catalog/i18n/pl.json +77 -0
- package/src/modules/catalog/lib/omnibus.ts +406 -0
- package/src/modules/catalog/lib/omnibusBackfill.ts +198 -0
- package/src/modules/catalog/lib/omnibusCache.ts +111 -0
- package/src/modules/catalog/lib/omnibusConfig.ts +73 -0
- package/src/modules/catalog/lib/omnibusHistoryQueries.ts +248 -0
- package/src/modules/catalog/lib/omnibusPresentedEntry.ts +76 -0
- package/src/modules/catalog/lib/omnibusProductListEnrichment.ts +125 -0
- package/src/modules/catalog/lib/omnibusTypes.ts +202 -0
- package/src/modules/catalog/lib/optionSchemaTranslations.ts +55 -0
- package/src/modules/catalog/lib/priceHistoryQuery.ts +155 -0
- package/src/modules/catalog/lib/pricing.ts +42 -4
- package/src/modules/catalog/lib/productFilters.ts +349 -0
- package/src/modules/catalog/lib/productScopeKeys.ts +162 -0
- package/src/modules/catalog/lib/productScopeReindex.ts +139 -0
- package/src/modules/catalog/migrations/.snapshot-open-mercato.json +572 -0
- package/src/modules/catalog/migrations/Migration20261005105352_catalog.ts +39 -0
- package/src/modules/catalog/migrations/Migration20261005150824_catalog.ts +39 -0
- package/src/modules/catalog/services/catalogOmnibusService.ts +642 -0
- package/src/modules/catalog/setup.ts +2 -0
- package/src/modules/catalog/subscribers/product-scope-keys-category-deleted.ts +18 -0
- package/src/modules/catalog/subscribers/product-scope-keys-category-updated.ts +19 -0
- package/src/modules/catalog/translations.ts +12 -0
- package/src/modules/configs/i18n/de.json +5 -1
- package/src/modules/configs/i18n/en.json +5 -1
- package/src/modules/configs/i18n/es.json +5 -1
- package/src/modules/configs/i18n/ko.json +5 -1
- package/src/modules/configs/i18n/pl.json +5 -1
- package/src/modules/configs/lib/upgrade-actions.ts +35 -0
- package/src/modules/customer_groups/api/customer-groups/[id]/terms/route.ts +7 -0
- package/src/modules/customer_groups/components/CustomerGroupTermsSection.tsx +124 -2
- package/src/modules/customer_groups/components/customerGroupAssortmentScope.ts +46 -0
- package/src/modules/customer_groups/data/entities.ts +4 -0
- package/src/modules/customer_groups/data/validators.ts +14 -0
- package/src/modules/customer_groups/i18n/de.json +12 -0
- package/src/modules/customer_groups/i18n/en.json +12 -0
- package/src/modules/customer_groups/i18n/es.json +12 -0
- package/src/modules/customer_groups/i18n/ko.json +12 -0
- package/src/modules/customer_groups/i18n/pl.json +12 -0
- package/src/modules/customer_groups/migrations/.snapshot-open-mercato.json +17 -0
- package/src/modules/customer_groups/migrations/Migration20261005110250_customer_groups.ts +15 -0
- package/src/modules/customer_groups/services/customerGroupsService.ts +74 -23
- package/src/modules/ecommerce/acl.ts +9 -0
- package/src/modules/ecommerce/api/domain-mappings/route.ts +76 -0
- package/src/modules/ecommerce/api/openapi.ts +28 -0
- package/src/modules/ecommerce/api/store-channel-bindings/[id]/assortment-count/route.ts +141 -0
- package/src/modules/ecommerce/api/store-channel-bindings/crud.ts +283 -0
- package/src/modules/ecommerce/api/store-channel-bindings/route.ts +67 -0
- package/src/modules/ecommerce/api/store-domain-bindings/crud.ts +324 -0
- package/src/modules/ecommerce/api/store-domain-bindings/route.ts +73 -0
- package/src/modules/ecommerce/api/storefront/categories/[slug]/route.ts +94 -0
- package/src/modules/ecommerce/api/storefront/categories/openapiSchemas.ts +63 -0
- package/src/modules/ecommerce/api/storefront/categories/route.ts +78 -0
- package/src/modules/ecommerce/api/storefront/context/route.ts +203 -0
- package/src/modules/ecommerce/api/storefront/products/[idOrHandle]/route.ts +94 -0
- package/src/modules/ecommerce/api/storefront/products/openapiSchemas.ts +193 -0
- package/src/modules/ecommerce/api/storefront/products/route.ts +81 -0
- package/src/modules/ecommerce/api/storefront/search/suggest/openapiSchemas.ts +25 -0
- package/src/modules/ecommerce/api/storefront/search/suggest/route.ts +78 -0
- package/src/modules/ecommerce/api/storefront/storefrontRouteSupport.ts +68 -0
- package/src/modules/ecommerce/api/stores/[id]/branding/route.ts +205 -0
- package/src/modules/ecommerce/api/stores/[id]/preview-branding/route.ts +99 -0
- package/src/modules/ecommerce/api/stores/crud.ts +457 -0
- package/src/modules/ecommerce/api/stores/route.ts +66 -0
- package/src/modules/ecommerce/backend/config/ecommerce/[id]/page.meta.ts +12 -0
- package/src/modules/ecommerce/backend/config/ecommerce/[id]/page.tsx +5 -0
- package/src/modules/ecommerce/backend/config/ecommerce/page.meta.ts +12 -0
- package/src/modules/ecommerce/backend/config/ecommerce/page.tsx +5 -0
- package/src/modules/ecommerce/commands/stores.ts +154 -0
- package/src/modules/ecommerce/components/ChannelAssortmentCount.tsx +138 -0
- package/src/modules/ecommerce/components/ChannelBindingFormSections.tsx +40 -0
- package/src/modules/ecommerce/components/ChannelLookupPicker.tsx +5 -0
- package/src/modules/ecommerce/components/DomainStatusWarning.tsx +32 -0
- package/src/modules/ecommerce/components/StoreAvailabilityDefaultsSection.tsx +219 -0
- package/src/modules/ecommerce/components/StoreBrandingControls.tsx +73 -0
- package/src/modules/ecommerce/components/StoreBrandingPreview.tsx +77 -0
- package/src/modules/ecommerce/components/StoreBrandingTab.tsx +242 -0
- package/src/modules/ecommerce/components/StoreChannelBindingDialog.tsx +306 -0
- package/src/modules/ecommerce/components/StoreChannelsTab.tsx +375 -0
- package/src/modules/ecommerce/components/StoreCreateDialog.tsx +204 -0
- package/src/modules/ecommerce/components/StoreDomainBindingDialog.tsx +198 -0
- package/src/modules/ecommerce/components/StoreDomainsTab.tsx +426 -0
- package/src/modules/ecommerce/components/StoreEditPage.tsx +193 -0
- package/src/modules/ecommerce/components/StoreGeneralTab.tsx +189 -0
- package/src/modules/ecommerce/components/StoreListPage.tsx +326 -0
- package/src/modules/ecommerce/components/StoreSeoTab.tsx +152 -0
- package/src/modules/ecommerce/components/StoreStatusBadge.tsx +22 -0
- package/src/modules/ecommerce/components/channelLookups.ts +24 -0
- package/src/modules/ecommerce/components/storeAdmin.ts +56 -0
- package/src/modules/ecommerce/components/storeBrandingForm.ts +230 -0
- package/src/modules/ecommerce/components/storeChannels.ts +210 -0
- package/src/modules/ecommerce/components/storeDomains.ts +221 -0
- package/src/modules/ecommerce/components/storeEditTabs.tsx +76 -0
- package/src/modules/ecommerce/components/storeGeneral.ts +155 -0
- package/src/modules/ecommerce/components/storeSeo.ts +78 -0
- package/src/modules/ecommerce/components/useChannelAssortmentCount.ts +67 -0
- package/src/modules/ecommerce/components/useStoreAccess.ts +36 -0
- package/src/modules/ecommerce/data/enrichers.ts +213 -0
- package/src/modules/ecommerce/data/entities.ts +168 -0
- package/src/modules/ecommerce/data/validators.ts +587 -0
- package/src/modules/ecommerce/di.ts +9 -0
- package/src/modules/ecommerce/events.ts +27 -0
- package/src/modules/ecommerce/i18n/de.json +341 -0
- package/src/modules/ecommerce/i18n/en.json +341 -0
- package/src/modules/ecommerce/i18n/es.json +341 -0
- package/src/modules/ecommerce/i18n/ko.json +341 -0
- package/src/modules/ecommerce/i18n/pl.json +341 -0
- package/src/modules/ecommerce/index.ts +14 -0
- package/src/modules/ecommerce/lib/assortmentCount.ts +175 -0
- package/src/modules/ecommerce/lib/brandingStyles.ts +215 -0
- package/src/modules/ecommerce/lib/buyerContext.ts +401 -0
- package/src/modules/ecommerce/lib/cacheInvalidation.ts +302 -0
- package/src/modules/ecommerce/lib/cacheKeys.ts +340 -0
- package/src/modules/ecommerce/lib/createConflictRecheck.ts +31 -0
- package/src/modules/ecommerce/lib/crudEvents.ts +128 -0
- package/src/modules/ecommerce/lib/crudSupport.ts +85 -0
- package/src/modules/ecommerce/lib/domainBindingRebind.ts +138 -0
- package/src/modules/ecommerce/lib/domainMappingSummaries.ts +71 -0
- package/src/modules/ecommerce/lib/exclusiveFlag.ts +90 -0
- package/src/modules/ecommerce/lib/references.ts +93 -0
- package/src/modules/ecommerce/lib/seedDraftStore.ts +125 -0
- package/src/modules/ecommerce/lib/storeBindingCascade.ts +138 -0
- package/src/modules/ecommerce/lib/storeBranding.ts +48 -0
- package/src/modules/ecommerce/lib/storeBrandingRoute.ts +91 -0
- package/src/modules/ecommerce/lib/storeContext.ts +669 -0
- package/src/modules/ecommerce/lib/storeContextService.ts +71 -0
- package/src/modules/ecommerce/lib/storeLocale.ts +88 -0
- package/src/modules/ecommerce/lib/storeNotifications.ts +55 -0
- package/src/modules/ecommerce/lib/storefrontCatalogSupport.ts +427 -0
- package/src/modules/ecommerce/lib/storefrontCategories.ts +419 -0
- package/src/modules/ecommerce/lib/storefrontCategoryCache.ts +113 -0
- package/src/modules/ecommerce/lib/storefrontDetail.ts +654 -0
- package/src/modules/ecommerce/lib/storefrontFacets.ts +697 -0
- package/src/modules/ecommerce/lib/storefrontOptionFilter.ts +44 -0
- package/src/modules/ecommerce/lib/storefrontPricing.ts +583 -0
- package/src/modules/ecommerce/lib/storefrontProductCache.ts +126 -0
- package/src/modules/ecommerce/lib/storefrontProductScope.ts +181 -0
- package/src/modules/ecommerce/lib/storefrontProducts.ts +963 -0
- package/src/modules/ecommerce/lib/storefrontQuery.ts +215 -0
- package/src/modules/ecommerce/lib/storefrontRateLimit.ts +126 -0
- package/src/modules/ecommerce/lib/storefrontSearch.ts +454 -0
- package/src/modules/ecommerce/lib/storefrontSearchCache.ts +49 -0
- package/src/modules/ecommerce/lib/subscriberSupport.ts +41 -0
- package/src/modules/ecommerce/lib/types.ts +69 -0
- package/src/modules/ecommerce/migrations/.snapshot-open-mercato.json +829 -0
- package/src/modules/ecommerce/migrations/Migration20261005113233_ecommerce.ts +32 -0
- package/src/modules/ecommerce/migrations/Migration20261006112733_ecommerce.ts +15 -0
- package/src/modules/ecommerce/notifications.client.ts +46 -0
- package/src/modules/ecommerce/notifications.ts +46 -0
- package/src/modules/ecommerce/search.ts +93 -0
- package/src/modules/ecommerce/setup.ts +15 -0
- package/src/modules/ecommerce/subscribers/assortment-empty-notifications.ts +12 -0
- package/src/modules/ecommerce/subscribers/availability-policy-cache-invalidation.ts +12 -0
- package/src/modules/ecommerce/subscribers/catalog-category-cache-invalidation.ts +12 -0
- package/src/modules/ecommerce/subscribers/catalog-price-cache-invalidation.ts +12 -0
- package/src/modules/ecommerce/subscribers/catalog-product-cache-invalidation.ts +12 -0
- package/src/modules/ecommerce/subscribers/catalog-variant-cache-invalidation.ts +12 -0
- package/src/modules/ecommerce/subscribers/customer-group-cache-invalidation.ts +12 -0
- package/src/modules/ecommerce/subscribers/customer-group-membership-cache-invalidation.ts +12 -0
- package/src/modules/ecommerce/subscribers/customer-group-terms-cache-invalidation.ts +12 -0
- package/src/modules/ecommerce/subscribers/customer-user-deleted-cache-invalidation.ts +12 -0
- package/src/modules/ecommerce/subscribers/customer-user-updated-cache-invalidation.ts +12 -0
- package/src/modules/ecommerce/subscribers/domain-binding-rebind.ts +14 -0
- package/src/modules/ecommerce/subscribers/domain-mapping-cache-invalidation.ts +12 -0
- package/src/modules/ecommerce/subscribers/store-cache-invalidation.ts +12 -0
- package/src/modules/ecommerce/subscribers/store-channel-binding-cache-invalidation.ts +12 -0
- package/src/modules/ecommerce/subscribers/store-domain-binding-cache-invalidation.ts +12 -0
- package/src/modules/ecommerce/subscribers/store-notifications.ts +12 -0
- package/src/modules/query_index/lib/batch.ts +25 -9
- package/src/modules/query_index/lib/doc-enrichers.ts +306 -0
- package/src/modules/query_index/lib/document.ts +2 -0
- package/src/modules/query_index/lib/engine.ts +58 -1
- package/src/modules/query_index/lib/indexer.ts +8 -0
- package/src/modules/query_index/lib/search-tokens.ts +2 -0
- package/src/modules/query_index/migrations/Migration20261005201500_query_index.ts +42 -0
- package/src/modules/translations/components/TranslationManager.tsx +3 -3
- package/src/modules/translations/data/validators.ts +1 -1
- package/src/modules/translations/lib/batch.ts +47 -0
- package/src/modules/translations/lib/helpers.ts +4 -0
- package/src/modules/translations/lib/resolve-field-list.ts +16 -2
|
@@ -0,0 +1,306 @@
|
|
|
1
|
+
import type { Kysely } from 'kysely'
|
|
2
|
+
import { createLogger } from '@open-mercato/shared/lib/logger'
|
|
3
|
+
import { recordIndexerError } from '@open-mercato/shared/lib/indexers/error-log'
|
|
4
|
+
import { getTelemetryRuntime } from '@open-mercato/shared/lib/telemetry/runtime'
|
|
5
|
+
|
|
6
|
+
const logger = createLogger('query_index').child({ component: 'doc-enrichers' })
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* Index-document enrichment — lets the module that owns an entity contribute computed keys to
|
|
10
|
+
* that entity's `entity_indexes.doc` at index time (Storefront Public API §3.3, decision D9).
|
|
11
|
+
*
|
|
12
|
+
* Registration is programmatic (call `registerIndexDocEnricher` from the owning module's `di.ts`);
|
|
13
|
+
* there is no auto-discovered module file for it.
|
|
14
|
+
*
|
|
15
|
+
* Key namespace: every enricher declares the exact keys it writes. A key MUST be a lowercase
|
|
16
|
+
* snake_case identifier (`^[a-z][a-z0-9_]*$`), so it can never collide with `cf:*` custom-field
|
|
17
|
+
* keys or `l10n:*` translation keys, MUST NOT be one of the reserved document keys, and MUST NOT be
|
|
18
|
+
* declared by another enricher of the same entity type. A key that turns out to be a base column
|
|
19
|
+
* of the indexed row is a collision detected at index time: the base value is kept, the
|
|
20
|
+
* contribution is discarded and the error is recorded.
|
|
21
|
+
*
|
|
22
|
+
* Failure semantics (fail closed, detectable): when an enricher throws, or returns nothing for a
|
|
23
|
+
* record, every key it declares is written as JSON `null` on the affected documents. Consumers
|
|
24
|
+
* MUST treat `null` (and an absent key) as "not indexed" — never as "empty". Indexing itself
|
|
25
|
+
* never fails because of an enricher.
|
|
26
|
+
*
|
|
27
|
+
* Enriched keys are filter/projection data: they are excluded from the `search_text` aggregate
|
|
28
|
+
* and from `search_tokens`.
|
|
29
|
+
*/
|
|
30
|
+
export type IndexDocEnricherContext = {
|
|
31
|
+
db: Kysely<any>
|
|
32
|
+
entityType: string
|
|
33
|
+
tenantId: string | null
|
|
34
|
+
organizationId: string | null
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
export type IndexDocEnricherRecord = {
|
|
38
|
+
recordId: string
|
|
39
|
+
doc: Readonly<Record<string, unknown>>
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
export type IndexDocEnrichment = Record<string, unknown>
|
|
43
|
+
|
|
44
|
+
export type IndexDocEnricher = {
|
|
45
|
+
id: string
|
|
46
|
+
entityType: string
|
|
47
|
+
keys: readonly string[]
|
|
48
|
+
enrich: (
|
|
49
|
+
records: readonly IndexDocEnricherRecord[],
|
|
50
|
+
ctx: IndexDocEnricherContext,
|
|
51
|
+
) => Promise<Map<string, IndexDocEnrichment>>
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
export type IndexDocEnrichmentTarget = {
|
|
55
|
+
recordId: string
|
|
56
|
+
doc: Record<string, unknown>
|
|
57
|
+
tenantId: string | null
|
|
58
|
+
organizationId: string | null
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
export const INDEX_DOC_ENRICHER_KEY_PATTERN = /^[a-z][a-z0-9_]*$/
|
|
62
|
+
|
|
63
|
+
export const RESERVED_INDEX_DOC_KEYS: ReadonlySet<string> = new Set([
|
|
64
|
+
'id',
|
|
65
|
+
'tenant_id',
|
|
66
|
+
'organization_id',
|
|
67
|
+
'created_at',
|
|
68
|
+
'updated_at',
|
|
69
|
+
'deleted_at',
|
|
70
|
+
'search_text',
|
|
71
|
+
])
|
|
72
|
+
|
|
73
|
+
type RegisteredEnricher = IndexDocEnricher & { keys: string[] }
|
|
74
|
+
|
|
75
|
+
type EnricherRegistryState = {
|
|
76
|
+
byEntityType: Map<string, RegisteredEnricher[]>
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
const GLOBAL_ENRICHER_REGISTRY_KEY = '__openMercatoQueryIndexDocEnrichers__' as const
|
|
80
|
+
|
|
81
|
+
type GlobalEnricherRegistry = typeof globalThis & {
|
|
82
|
+
[GLOBAL_ENRICHER_REGISTRY_KEY]?: EnricherRegistryState
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
function getRegistryState(): EnricherRegistryState {
|
|
86
|
+
const globalRegistry = globalThis as GlobalEnricherRegistry
|
|
87
|
+
if (!globalRegistry[GLOBAL_ENRICHER_REGISTRY_KEY]) {
|
|
88
|
+
globalRegistry[GLOBAL_ENRICHER_REGISTRY_KEY] = { byEntityType: new Map() }
|
|
89
|
+
}
|
|
90
|
+
return globalRegistry[GLOBAL_ENRICHER_REGISTRY_KEY]
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
function validateEnricher(enricher: IndexDocEnricher, existing: readonly RegisteredEnricher[]): string[] {
|
|
94
|
+
if (typeof enricher.id !== 'string' || !enricher.id.trim()) {
|
|
95
|
+
throw new Error('[internal] Index doc enricher requires a non-empty id')
|
|
96
|
+
}
|
|
97
|
+
if (typeof enricher.entityType !== 'string' || !enricher.entityType.includes(':')) {
|
|
98
|
+
throw new Error(`[internal] Index doc enricher "${enricher.id}" requires an entityType in '<module>:<entity>' form`)
|
|
99
|
+
}
|
|
100
|
+
if (typeof enricher.enrich !== 'function') {
|
|
101
|
+
throw new Error(`[internal] Index doc enricher "${enricher.id}" requires an enrich function`)
|
|
102
|
+
}
|
|
103
|
+
const keys = Array.from(new Set(enricher.keys ?? []))
|
|
104
|
+
if (!keys.length) {
|
|
105
|
+
throw new Error(`[internal] Index doc enricher "${enricher.id}" must declare at least one key`)
|
|
106
|
+
}
|
|
107
|
+
for (const key of keys) {
|
|
108
|
+
if (!INDEX_DOC_ENRICHER_KEY_PATTERN.test(key)) {
|
|
109
|
+
throw new Error(`[internal] Index doc enricher "${enricher.id}" key "${key}" must match ${INDEX_DOC_ENRICHER_KEY_PATTERN}`)
|
|
110
|
+
}
|
|
111
|
+
if (RESERVED_INDEX_DOC_KEYS.has(key)) {
|
|
112
|
+
throw new Error(`[internal] Index doc enricher "${enricher.id}" key "${key}" is reserved`)
|
|
113
|
+
}
|
|
114
|
+
const owner = existing.find((entry) => entry.id !== enricher.id && entry.keys.includes(key))
|
|
115
|
+
if (owner) {
|
|
116
|
+
throw new Error(`[internal] Index doc enricher "${enricher.id}" key "${key}" is already contributed by "${owner.id}" for ${enricher.entityType}`)
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
return keys
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
/**
|
|
123
|
+
* Registers an enricher. Re-registering the same `id` for the same entity type replaces the
|
|
124
|
+
* previous registration (HMR-safe). Throws on an invalid or colliding key declaration.
|
|
125
|
+
* Returns a function that removes the registration.
|
|
126
|
+
*/
|
|
127
|
+
export function registerIndexDocEnricher(enricher: IndexDocEnricher): () => void {
|
|
128
|
+
const state = getRegistryState()
|
|
129
|
+
const existing = state.byEntityType.get(enricher.entityType) ?? []
|
|
130
|
+
const keys = validateEnricher(enricher, existing)
|
|
131
|
+
const registered: RegisteredEnricher = { ...enricher, keys }
|
|
132
|
+
const next = existing.filter((entry) => entry.id !== enricher.id)
|
|
133
|
+
next.push(registered)
|
|
134
|
+
state.byEntityType.set(enricher.entityType, next)
|
|
135
|
+
return () => {
|
|
136
|
+
const current = state.byEntityType.get(enricher.entityType) ?? []
|
|
137
|
+
const remaining = current.filter((entry) => entry !== registered)
|
|
138
|
+
if (remaining.length) state.byEntityType.set(enricher.entityType, remaining)
|
|
139
|
+
else state.byEntityType.delete(enricher.entityType)
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
export function getIndexDocEnrichers(entityType: string): readonly IndexDocEnricher[] {
|
|
144
|
+
return getRegistryState().byEntityType.get(entityType) ?? []
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
export function hasIndexDocEnrichers(entityType: string): boolean {
|
|
148
|
+
return (getRegistryState().byEntityType.get(entityType)?.length ?? 0) > 0
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
export function isIndexDocEnrichedKey(entityType: string | null | undefined, field: string): boolean {
|
|
152
|
+
if (!entityType) return false
|
|
153
|
+
const enrichers = getRegistryState().byEntityType.get(entityType)
|
|
154
|
+
if (!enrichers) return false
|
|
155
|
+
return enrichers.some((entry) => entry.keys.includes(field))
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
export function resetIndexDocEnrichers(): void {
|
|
159
|
+
getRegistryState().byEntityType.clear()
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
function scopeGroupKey(target: IndexDocEnrichmentTarget): string {
|
|
163
|
+
return `${target.tenantId ?? ''}|${target.organizationId ?? ''}`
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
async function reportEnricherFailure(
|
|
167
|
+
db: Kysely<any>,
|
|
168
|
+
input: {
|
|
169
|
+
enricher: RegisteredEnricher
|
|
170
|
+
entityType: string
|
|
171
|
+
tenantId: string | null
|
|
172
|
+
organizationId: string | null
|
|
173
|
+
recordId?: string
|
|
174
|
+
error: unknown
|
|
175
|
+
handler: string
|
|
176
|
+
details?: Record<string, unknown>
|
|
177
|
+
},
|
|
178
|
+
): Promise<void> {
|
|
179
|
+
logger.error('Index doc enricher failed; contributed keys written as null', {
|
|
180
|
+
enricherId: input.enricher.id,
|
|
181
|
+
entityType: input.entityType,
|
|
182
|
+
tenantId: input.tenantId,
|
|
183
|
+
organizationId: input.organizationId,
|
|
184
|
+
recordId: input.recordId ?? null,
|
|
185
|
+
...input.details,
|
|
186
|
+
err: input.error,
|
|
187
|
+
})
|
|
188
|
+
getTelemetryRuntime()?.reportError(input.error, {
|
|
189
|
+
module: 'query_index',
|
|
190
|
+
code: 'query_index.doc_enricher_failed',
|
|
191
|
+
})
|
|
192
|
+
await recordIndexerError(
|
|
193
|
+
{ db },
|
|
194
|
+
{
|
|
195
|
+
source: 'query_index',
|
|
196
|
+
handler: input.handler,
|
|
197
|
+
error: input.error,
|
|
198
|
+
entityType: input.entityType,
|
|
199
|
+
recordId: input.recordId ?? null,
|
|
200
|
+
tenantId: input.tenantId,
|
|
201
|
+
organizationId: input.organizationId,
|
|
202
|
+
payload: { enricherId: input.enricher.id, ...input.details },
|
|
203
|
+
},
|
|
204
|
+
).catch(() => undefined)
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
const COLLISION_SAMPLE_SIZE = 10
|
|
208
|
+
|
|
209
|
+
function nullOut(targets: readonly IndexDocEnrichmentTarget[], keys: readonly string[], baseKeys: Map<string, Set<string>>): void {
|
|
210
|
+
for (const target of targets) {
|
|
211
|
+
const ownKeys = baseKeys.get(target.recordId)
|
|
212
|
+
for (const key of keys) {
|
|
213
|
+
if (ownKeys?.has(key)) continue
|
|
214
|
+
target.doc[key] = null
|
|
215
|
+
}
|
|
216
|
+
}
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
/**
|
|
220
|
+
* Runs every enricher registered for `entityType` over a batch of freshly built documents and
|
|
221
|
+
* merges the contributed keys into them in place. One `enrich` call per enricher per
|
|
222
|
+
* tenant/organization scope present in the batch — never per record.
|
|
223
|
+
*/
|
|
224
|
+
export async function applyIndexDocEnrichers(
|
|
225
|
+
db: Kysely<any>,
|
|
226
|
+
entityType: string,
|
|
227
|
+
targets: readonly IndexDocEnrichmentTarget[],
|
|
228
|
+
): Promise<void> {
|
|
229
|
+
const enrichers = getRegistryState().byEntityType.get(entityType)
|
|
230
|
+
if (!enrichers?.length || !targets.length) return
|
|
231
|
+
|
|
232
|
+
const baseKeys = new Map<string, Set<string>>()
|
|
233
|
+
for (const target of targets) baseKeys.set(target.recordId, new Set(Object.keys(target.doc)))
|
|
234
|
+
|
|
235
|
+
const groups = new Map<string, IndexDocEnrichmentTarget[]>()
|
|
236
|
+
for (const target of targets) {
|
|
237
|
+
const key = scopeGroupKey(target)
|
|
238
|
+
const bucket = groups.get(key)
|
|
239
|
+
if (bucket) bucket.push(target)
|
|
240
|
+
else groups.set(key, [target])
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
for (const enricher of [...enrichers]) {
|
|
244
|
+
for (const group of groups.values()) {
|
|
245
|
+
const { tenantId, organizationId } = group[0]
|
|
246
|
+
let result: Map<string, IndexDocEnrichment>
|
|
247
|
+
try {
|
|
248
|
+
const records = group.map((target) => ({ recordId: target.recordId, doc: target.doc }))
|
|
249
|
+
const returned = await enricher.enrich(records, { db, entityType, tenantId, organizationId })
|
|
250
|
+
if (!(returned instanceof Map)) {
|
|
251
|
+
throw new Error(`[internal] Index doc enricher "${enricher.id}" must return a Map keyed by record id`)
|
|
252
|
+
}
|
|
253
|
+
result = returned
|
|
254
|
+
} catch (error) {
|
|
255
|
+
nullOut(group, enricher.keys, baseKeys)
|
|
256
|
+
await reportEnricherFailure(db, {
|
|
257
|
+
enricher, entityType, tenantId, organizationId, error,
|
|
258
|
+
handler: 'query_index:doc-enricher',
|
|
259
|
+
})
|
|
260
|
+
continue
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
const collidingRecordIdsByKey = new Map<string, string[]>()
|
|
264
|
+
for (const target of group) {
|
|
265
|
+
const contribution = result.get(target.recordId)
|
|
266
|
+
const ownKeys = baseKeys.get(target.recordId)
|
|
267
|
+
for (const key of enricher.keys) {
|
|
268
|
+
if (ownKeys?.has(key)) {
|
|
269
|
+
const colliding = collidingRecordIdsByKey.get(key)
|
|
270
|
+
if (colliding) colliding.push(target.recordId)
|
|
271
|
+
else collidingRecordIdsByKey.set(key, [target.recordId])
|
|
272
|
+
continue
|
|
273
|
+
}
|
|
274
|
+
const value = contribution && Object.prototype.hasOwnProperty.call(contribution, key)
|
|
275
|
+
? contribution[key]
|
|
276
|
+
: null
|
|
277
|
+
target.doc[key] = value === undefined ? null : value
|
|
278
|
+
}
|
|
279
|
+
if (contribution) {
|
|
280
|
+
const undeclared = Object.keys(contribution).filter((key) => !enricher.keys.includes(key))
|
|
281
|
+
if (undeclared.length) {
|
|
282
|
+
logger.warn('Index doc enricher returned undeclared keys; ignored', {
|
|
283
|
+
enricherId: enricher.id,
|
|
284
|
+
entityType,
|
|
285
|
+
recordId: target.recordId,
|
|
286
|
+
keys: undeclared,
|
|
287
|
+
})
|
|
288
|
+
}
|
|
289
|
+
}
|
|
290
|
+
}
|
|
291
|
+
for (const [key, recordIds] of collidingRecordIdsByKey) {
|
|
292
|
+
await reportEnricherFailure(db, {
|
|
293
|
+
enricher, entityType, tenantId, organizationId,
|
|
294
|
+
recordId: recordIds.length === 1 ? recordIds[0] : undefined,
|
|
295
|
+
error: new Error(`[internal] Index doc enricher "${enricher.id}" key "${key}" collides with a base document key of ${entityType} on ${recordIds.length} record(s)`),
|
|
296
|
+
handler: 'query_index:doc-enricher:collision',
|
|
297
|
+
details: {
|
|
298
|
+
collidingKey: key,
|
|
299
|
+
collidingRecordCount: recordIds.length,
|
|
300
|
+
sampleRecordIds: recordIds.slice(0, COLLISION_SAMPLE_SIZE),
|
|
301
|
+
},
|
|
302
|
+
})
|
|
303
|
+
}
|
|
304
|
+
}
|
|
305
|
+
}
|
|
306
|
+
}
|
|
@@ -3,6 +3,7 @@ import {
|
|
|
3
3
|
resolveSearchConfig,
|
|
4
4
|
type SearchConfig,
|
|
5
5
|
} from '@open-mercato/shared/lib/search/config'
|
|
6
|
+
import { isIndexDocEnrichedKey } from './doc-enrichers'
|
|
6
7
|
|
|
7
8
|
export type IndexDocumentScope = {
|
|
8
9
|
organizationId?: string | null
|
|
@@ -67,6 +68,7 @@ function collectAggregateSearchValues(
|
|
|
67
68
|
}
|
|
68
69
|
|
|
69
70
|
if (isSearchFieldBlocklisted(field, entityType, config)) return []
|
|
71
|
+
if (isIndexDocEnrichedKey(entityType, field)) return []
|
|
70
72
|
|
|
71
73
|
if (typeof value === 'string') {
|
|
72
74
|
const trimmed = value.trim()
|
|
@@ -4,6 +4,13 @@ import type { EntityId } from '@open-mercato/shared/modules/entities'
|
|
|
4
4
|
import type { EntityManager } from '@mikro-orm/postgresql'
|
|
5
5
|
import { BasicQueryEngine, resolveEntityTableName, resolveRegisteredEntityTableName } from '@open-mercato/shared/lib/query/engine'
|
|
6
6
|
import { isOrmBackedSystemEntityId } from '@open-mercato/shared/lib/data/engine'
|
|
7
|
+
import {
|
|
8
|
+
buildArrayColumnNoOverlapPredicate,
|
|
9
|
+
buildArrayColumnOverlapPredicate,
|
|
10
|
+
buildJsonbNoOverlapPredicate,
|
|
11
|
+
buildJsonbOverlapPredicate,
|
|
12
|
+
normalizeOverlapValues,
|
|
13
|
+
} from '@open-mercato/shared/lib/query/overlap'
|
|
7
14
|
import { type Kysely, sql, type RawBuilder } from 'kysely'
|
|
8
15
|
import type { EventBus } from '@open-mercato/events'
|
|
9
16
|
import { readCoverageSnapshot, refreshCoverageSnapshot } from './coverage'
|
|
@@ -70,7 +77,7 @@ type CustomFieldDefRow = {
|
|
|
70
77
|
|
|
71
78
|
/** Operators `buildCfFilterExpression` compiles; anything else yields no predicate. */
|
|
72
79
|
const CF_FILTER_SUPPORTED_OPS = new Set<FilterOp>([
|
|
73
|
-
'eq', 'ne', 'in', 'nin', 'like', 'ilike', 'exists', 'gt', 'gte', 'lt', 'lte',
|
|
80
|
+
'eq', 'ne', 'in', 'nin', 'like', 'ilike', 'exists', 'gt', 'gte', 'lt', 'lte', 'overlap', 'noverlap',
|
|
74
81
|
])
|
|
75
82
|
|
|
76
83
|
/** Custom field kinds stored numerically (`custom_field_values.value_int`/`value_float`) — sort numerically, not as text (#5674). */
|
|
@@ -1007,6 +1014,8 @@ export class HybridQueryEngine implements QueryEngine {
|
|
|
1007
1014
|
case 'like': return target.where(column, 'like', value as any)
|
|
1008
1015
|
case 'ilike': return target.where(column, 'ilike', value as any)
|
|
1009
1016
|
case 'exists': return value ? target.where(column, 'is not', null) : target.where(column, 'is', null)
|
|
1017
|
+
case 'overlap': return target.where(buildArrayColumnOverlapPredicate(column, value))
|
|
1018
|
+
case 'noverlap': return target.where(buildArrayColumnNoOverlapPredicate(column, value))
|
|
1010
1019
|
default: return target
|
|
1011
1020
|
}
|
|
1012
1021
|
}
|
|
@@ -1710,6 +1719,18 @@ export class HybridQueryEngine implements QueryEngine {
|
|
|
1710
1719
|
])
|
|
1711
1720
|
)
|
|
1712
1721
|
}
|
|
1722
|
+
case 'noverlap':
|
|
1723
|
+
return sql<boolean>`false`
|
|
1724
|
+
case 'overlap': {
|
|
1725
|
+
const values = normalizeOverlapValues(value)
|
|
1726
|
+
if (!values.length) return sql<boolean>`false`
|
|
1727
|
+
return eb.or(
|
|
1728
|
+
values.flatMap((val) => [
|
|
1729
|
+
sql<boolean>`${textExpr} = ${val}`,
|
|
1730
|
+
arrContains(val),
|
|
1731
|
+
])
|
|
1732
|
+
)
|
|
1733
|
+
}
|
|
1713
1734
|
case 'nin': {
|
|
1714
1735
|
const values = this.toArray(value)
|
|
1715
1736
|
return sql<boolean>`${textExpr} not in (${sql.join(values.map((v) => sql`${v}`), sql`, `)})`
|
|
@@ -1886,6 +1907,18 @@ export class HybridQueryEngine implements QueryEngine {
|
|
|
1886
1907
|
])
|
|
1887
1908
|
))
|
|
1888
1909
|
}
|
|
1910
|
+
case 'noverlap':
|
|
1911
|
+
return q.where(sql<boolean>`false`)
|
|
1912
|
+
case 'overlap': {
|
|
1913
|
+
const vals = normalizeOverlapValues(value)
|
|
1914
|
+
if (!vals.length) return q.where(sql<boolean>`false`)
|
|
1915
|
+
return q.where((eb: any) => eb.or(
|
|
1916
|
+
vals.flatMap((val) => [
|
|
1917
|
+
sql<boolean>`${textExpr} = ${val}`,
|
|
1918
|
+
arrContains(val),
|
|
1919
|
+
])
|
|
1920
|
+
))
|
|
1921
|
+
}
|
|
1889
1922
|
case 'nin': {
|
|
1890
1923
|
const vals = this.toArray(value)
|
|
1891
1924
|
return q.where(sql<boolean>`${textExpr} not in (${sql.join(vals.map((v) => sql`${v}`), sql`, `)})`)
|
|
@@ -1954,6 +1987,10 @@ export class HybridQueryEngine implements QueryEngine {
|
|
|
1954
1987
|
const vals = this.toArray(value)
|
|
1955
1988
|
return q.where(sql<boolean>`${textExpr} in (${sql.join(vals.map((v) => sql`${v}`), sql`, `)})`)
|
|
1956
1989
|
}
|
|
1990
|
+
case 'overlap':
|
|
1991
|
+
return q.where(buildJsonbOverlapPredicate(`${alias}.doc`, key, value))
|
|
1992
|
+
case 'noverlap':
|
|
1993
|
+
return q.where(buildJsonbNoOverlapPredicate(`${alias}.doc`, key, value))
|
|
1957
1994
|
case 'nin': {
|
|
1958
1995
|
const vals = this.toArray(value)
|
|
1959
1996
|
return q.where(sql<boolean>`${textExpr} not in (${sql.join(vals.map((v) => sql`${v}`), sql`, `)})`)
|
|
@@ -2071,6 +2108,8 @@ export class HybridQueryEngine implements QueryEngine {
|
|
|
2071
2108
|
case 'like': return eb(column, 'like', value)
|
|
2072
2109
|
case 'ilike': return eb(column, 'ilike', value)
|
|
2073
2110
|
case 'exists': return eb(column, value ? 'is not' : 'is', null)
|
|
2111
|
+
case 'overlap': return buildArrayColumnOverlapPredicate(column, value)
|
|
2112
|
+
case 'noverlap': return buildArrayColumnNoOverlapPredicate(column, value)
|
|
2074
2113
|
default: return sql<boolean>`true`
|
|
2075
2114
|
}
|
|
2076
2115
|
}
|
|
@@ -2102,6 +2141,10 @@ export class HybridQueryEngine implements QueryEngine {
|
|
|
2102
2141
|
const vals = this.toArray(value)
|
|
2103
2142
|
return sql<boolean>`${textExpr} in (${sql.join(vals.map((v) => sql`${v}`), sql`, `)})`
|
|
2104
2143
|
}
|
|
2144
|
+
case 'overlap':
|
|
2145
|
+
return buildJsonbOverlapPredicate(`${alias}.doc`, key, value)
|
|
2146
|
+
case 'noverlap':
|
|
2147
|
+
return buildJsonbNoOverlapPredicate(`${alias}.doc`, key, value)
|
|
2105
2148
|
case 'nin': {
|
|
2106
2149
|
const vals = this.toArray(value)
|
|
2107
2150
|
return sql<boolean>`${textExpr} not in (${sql.join(vals.map((v) => sql`${v}`), sql`, `)})`
|
|
@@ -2158,6 +2201,14 @@ export class HybridQueryEngine implements QueryEngine {
|
|
|
2158
2201
|
})
|
|
2159
2202
|
continue
|
|
2160
2203
|
}
|
|
2204
|
+
if (filter.op === 'overlap') {
|
|
2205
|
+
next = next.where(buildJsonbOverlapPredicate(`${alias}.doc`, String(filter.field), filter.value))
|
|
2206
|
+
continue
|
|
2207
|
+
}
|
|
2208
|
+
if (filter.op === 'noverlap') {
|
|
2209
|
+
next = next.where(buildJsonbNoOverlapPredicate(`${alias}.doc`, String(filter.field), filter.value))
|
|
2210
|
+
continue
|
|
2211
|
+
}
|
|
2161
2212
|
// Unknown field → filter on doc JSON text
|
|
2162
2213
|
const docExpr = sql<string | null>`(${sql.ref(alias + '.doc')} ->> ${String(filter.field)})`
|
|
2163
2214
|
next = this.applyColumnFilter(next, docExpr, filter, {
|
|
@@ -2748,6 +2799,8 @@ export class HybridQueryEngine implements QueryEngine {
|
|
|
2748
2799
|
case '$like': add(field, 'like', opVal); break
|
|
2749
2800
|
case '$ilike': add(field, 'ilike', opVal); break
|
|
2750
2801
|
case '$exists': add(field, 'exists', opVal); break
|
|
2802
|
+
case '$overlap': add(field, 'overlap', opVal); break
|
|
2803
|
+
case '$noverlap': add(field, 'noverlap', opVal); break
|
|
2751
2804
|
}
|
|
2752
2805
|
}
|
|
2753
2806
|
} else {
|
|
@@ -2889,6 +2942,10 @@ export class HybridQueryEngine implements QueryEngine {
|
|
|
2889
2942
|
return q.where(col, 'ilike', filter.value as any)
|
|
2890
2943
|
case 'exists':
|
|
2891
2944
|
return filter.value ? q.where(col, 'is not', null) : q.where(col, 'is', null)
|
|
2945
|
+
case 'overlap':
|
|
2946
|
+
return q.where(buildArrayColumnOverlapPredicate(col, filter.value))
|
|
2947
|
+
case 'noverlap':
|
|
2948
|
+
return q.where(buildArrayColumnNoOverlapPredicate(col, filter.value))
|
|
2892
2949
|
default:
|
|
2893
2950
|
return q
|
|
2894
2951
|
}
|
|
@@ -13,6 +13,7 @@ import { createLogger } from '@open-mercato/shared/lib/logger'
|
|
|
13
13
|
import { resolveSearchConfig } from '@open-mercato/shared/lib/search/config'
|
|
14
14
|
import { replaceSearchTokensForRecord, deleteSearchTokensForRecord } from './search-tokens'
|
|
15
15
|
import { attachAggregateSearchField, rebuildAggregateSearchField } from './document'
|
|
16
|
+
import { applyIndexDocEnrichers } from './doc-enrichers'
|
|
16
17
|
|
|
17
18
|
const logger = createLogger('query_index').child({ component: 'indexer' })
|
|
18
19
|
|
|
@@ -198,6 +199,13 @@ export async function buildIndexDoc(em: EntityManager, params: BuildDocParams):
|
|
|
198
199
|
}
|
|
199
200
|
} catch {}
|
|
200
201
|
|
|
202
|
+
await applyIndexDocEnrichers(db, params.entityType, [{
|
|
203
|
+
recordId: String(params.recordId),
|
|
204
|
+
doc,
|
|
205
|
+
tenantId: params.tenantId ?? null,
|
|
206
|
+
organizationId: params.organizationId ?? null,
|
|
207
|
+
}])
|
|
208
|
+
|
|
201
209
|
// Kept outside the guard below: a failure while building the aggregate search field is a
|
|
202
210
|
// bug in the aggregation or its configuration, and must surface instead of being mistaken
|
|
203
211
|
// for an encryption failure and silently skipping encryption.
|
|
@@ -10,6 +10,7 @@ import { looksLikeEncryptedPayload } from '@open-mercato/shared/lib/encryption/a
|
|
|
10
10
|
import { createKmsService, resolveEncryptionMode, type KmsService } from '@open-mercato/shared/lib/encryption/kms'
|
|
11
11
|
import { parseBooleanToken } from '@open-mercato/shared/lib/boolean'
|
|
12
12
|
import { createLogger } from '@open-mercato/shared/lib/logger'
|
|
13
|
+
import { isIndexDocEnrichedKey } from './doc-enrichers'
|
|
13
14
|
|
|
14
15
|
const logger = createLogger('query_index').child({ component: 'search-tokens' })
|
|
15
16
|
|
|
@@ -166,6 +167,7 @@ function shouldIndexField(
|
|
|
166
167
|
if (lower.endsWith('_at')) return false
|
|
167
168
|
if (['created_at', 'updated_at', 'deleted_at', 'tenant_id', 'organization_id'].includes(lower)) return false
|
|
168
169
|
if (isSearchFieldBlocklisted(field, entityType, config)) return false
|
|
170
|
+
if (isIndexDocEnrichedKey(entityType, field)) return false
|
|
169
171
|
return collectTextValues(value).some((text) => text.length > 0)
|
|
170
172
|
}
|
|
171
173
|
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import { Migration } from '@mikro-orm/migrations';
|
|
2
|
+
|
|
3
|
+
// GIN expression index serving the storefront assortment predicate (Storefront Public API
|
|
4
|
+
// §3.3, D9): `(doc -> 'scope_keys') ?| $n::text[]` on catalog:catalog_product index documents,
|
|
5
|
+
// whose `scope_keys` key the catalog contributes through its doc enricher
|
|
6
|
+
// (catalog/lib/productScopeKeys.ts).
|
|
7
|
+
//
|
|
8
|
+
// Ownership: query_index owns the migration (as the spec's §3.3 table assigns it) because it
|
|
9
|
+
// owns `entity_indexes`; a catalog migration cannot do it, since modules migrate in
|
|
10
|
+
// alphabetical order and on a fresh database catalog's migrations run before query_index
|
|
11
|
+
// creates the table. MANUAL DDL: a partial expression index keyed on another module's entity
|
|
12
|
+
// type is not modelled on the EntityIndexRow entity, so `.snapshot-open-mercato.json` does not
|
|
13
|
+
// record it; `yarn db:generate` diffs entity metadata against the snapshot, so it never emits a
|
|
14
|
+
// drop for it (same approach as the Omnibus trigger DDL in catalog's
|
|
15
|
+
// Migration20261005150824_catalog).
|
|
16
|
+
//
|
|
17
|
+
// The expression must be spelled exactly as the query engine emits it
|
|
18
|
+
// (`jsonbDocKeyExpression` in @open-mercato/shared/lib/query/overlap emits the key as a
|
|
19
|
+
// literal) or PostgreSQL silently falls back to a sequential scan; the catalog
|
|
20
|
+
// product-scope-keys-index test pins both sides. Default `jsonb_ops` (not `jsonb_path_ops`),
|
|
21
|
+
// because only `jsonb_ops` supports `?|`.
|
|
22
|
+
//
|
|
23
|
+
// entity_indexes is high-churn, so the index is built CONCURRENTLY, which cannot run inside a
|
|
24
|
+
// transaction (isTransactional() => false; the runner applies migrations one-by-one). Drop
|
|
25
|
+
// first so retrying an interrupted build removes PostgreSQL's INVALID index stub. Same shape as
|
|
26
|
+
// Migration20260731105052_query_index.
|
|
27
|
+
export class Migration20261005201500_query_index extends Migration {
|
|
28
|
+
|
|
29
|
+
override isTransactional(): boolean {
|
|
30
|
+
return false;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
override up(): void | Promise<void> {
|
|
34
|
+
this.addSql(`drop index concurrently if exists "entity_indexes_catalog_product_scope_keys_gin_idx";`);
|
|
35
|
+
this.addSql(`create index concurrently "entity_indexes_catalog_product_scope_keys_gin_idx" on "entity_indexes" using gin (("doc" -> 'scope_keys')) where "entity_type" = 'catalog:catalog_product';`);
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
override down(): void | Promise<void> {
|
|
39
|
+
this.addSql(`drop index concurrently if exists "entity_indexes_catalog_product_scope_keys_gin_idx";`);
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
}
|
|
@@ -190,8 +190,8 @@ export function TranslationManager({
|
|
|
190
190
|
})
|
|
191
191
|
|
|
192
192
|
const fieldList = React.useMemo(
|
|
193
|
-
() => resolveFieldList(entityType, propTranslatableFields, fieldDefs as Array<{ key: string; kind: string; label?: string }
|
|
194
|
-
[entityType, propTranslatableFields, fieldDefs],
|
|
193
|
+
() => resolveFieldList(entityType, propTranslatableFields, fieldDefs as Array<{ key: string; kind: string; label?: string }>, baseValues),
|
|
194
|
+
[entityType, propTranslatableFields, fieldDefs, baseValues],
|
|
195
195
|
)
|
|
196
196
|
|
|
197
197
|
const {
|
|
@@ -440,7 +440,7 @@ export function TranslationManager({
|
|
|
440
440
|
</thead>
|
|
441
441
|
<tbody>
|
|
442
442
|
{fieldList.map((field) => {
|
|
443
|
-
const baseVal = getBaseValue(field.key)
|
|
443
|
+
const baseVal = field.baseValue ?? getBaseValue(field.key)
|
|
444
444
|
const translatedVal = localeTranslations[field.key] ?? ''
|
|
445
445
|
|
|
446
446
|
return (
|
|
@@ -23,3 +23,50 @@ export async function batchLoadTranslations(
|
|
|
23
23
|
}
|
|
24
24
|
return map
|
|
25
25
|
}
|
|
26
|
+
|
|
27
|
+
type EntityTranslationsTable = {
|
|
28
|
+
entity_translations: {
|
|
29
|
+
entity_type: string
|
|
30
|
+
entity_id: string
|
|
31
|
+
translations: Record<string, Record<string, unknown>> | null
|
|
32
|
+
tenant_id: string | null
|
|
33
|
+
organization_id: string | null
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
* Multi-entity-type variant of `batchLoadTranslations`: every request is answered by one query,
|
|
39
|
+
* keyed by entity type and then entity id. Organization matching is exact, as in the single-type helper.
|
|
40
|
+
*/
|
|
41
|
+
export async function batchLoadTranslationsMany(
|
|
42
|
+
db: Kysely<EntityTranslationsTable>,
|
|
43
|
+
requests: Array<{ entityType: string; entityIds: string[] }>,
|
|
44
|
+
scope: { tenantId?: string | null; organizationId?: string | null },
|
|
45
|
+
): Promise<Map<string, Map<string, Record<string, Record<string, unknown>>>>> {
|
|
46
|
+
const result = new Map<string, Map<string, Record<string, Record<string, unknown>>>>()
|
|
47
|
+
const wanted = new Map<string, Set<string>>()
|
|
48
|
+
for (const request of requests) {
|
|
49
|
+
if (!result.has(request.entityType)) result.set(request.entityType, new Map())
|
|
50
|
+
const ids = wanted.get(request.entityType) ?? new Set<string>()
|
|
51
|
+
for (const id of request.entityIds) ids.add(id)
|
|
52
|
+
wanted.set(request.entityType, ids)
|
|
53
|
+
}
|
|
54
|
+
const entityTypes = Array.from(wanted.entries()).filter(([, ids]) => ids.size > 0).map(([entityType]) => entityType)
|
|
55
|
+
if (!entityTypes.length) return result
|
|
56
|
+
const entityIds = Array.from(new Set(entityTypes.flatMap((entityType) => Array.from(wanted.get(entityType) ?? []))))
|
|
57
|
+
|
|
58
|
+
const rows = await db
|
|
59
|
+
.selectFrom('entity_translations')
|
|
60
|
+
.select(['entity_type', 'entity_id', 'translations'])
|
|
61
|
+
.where('entity_type', 'in', entityTypes)
|
|
62
|
+
.where('entity_id', 'in', entityIds)
|
|
63
|
+
.where(sql<boolean>`tenant_id is not distinct from ${scope.tenantId ?? null}`)
|
|
64
|
+
.where(sql<boolean>`organization_id is not distinct from ${scope.organizationId ?? null}`)
|
|
65
|
+
.execute()
|
|
66
|
+
|
|
67
|
+
for (const row of rows) {
|
|
68
|
+
if (!wanted.get(row.entity_type)?.has(row.entity_id)) continue
|
|
69
|
+
result.get(row.entity_type)?.set(row.entity_id, row.translations ?? {})
|
|
70
|
+
}
|
|
71
|
+
return result
|
|
72
|
+
}
|
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
import { getTranslatableEntityListPath } from '@open-mercato/shared/lib/localization/translatable-fields'
|
|
2
|
+
|
|
1
3
|
export function formatFieldLabel(field: string): string {
|
|
2
4
|
if (field.includes('.')) {
|
|
3
5
|
const parts = field.split('.')
|
|
@@ -45,6 +47,8 @@ function pluralize(word: string): string {
|
|
|
45
47
|
export function buildEntityListUrl(entityType: string): string | null {
|
|
46
48
|
const [module, entity] = entityType.split(':')
|
|
47
49
|
if (!module || !entity) return null
|
|
50
|
+
const registeredPath = getTranslatableEntityListPath(entityType)
|
|
51
|
+
if (registeredPath) return registeredPath
|
|
48
52
|
const prefix = `${module}_`
|
|
49
53
|
const base = entity.startsWith(prefix) ? entity.slice(prefix.length) : entity
|
|
50
54
|
const resource = pluralize(base).replace(/_/g, '-')
|
|
@@ -1,9 +1,9 @@
|
|
|
1
|
-
import { getTranslatableFields } from '@open-mercato/shared/lib/localization/translatable-fields'
|
|
1
|
+
import { getTranslatableFieldExpander, getTranslatableFields } from '@open-mercato/shared/lib/localization/translatable-fields'
|
|
2
2
|
import { getEntityFields } from '#generated/entity-fields-registry'
|
|
3
3
|
import { isTranslatableField } from './translatable-fields'
|
|
4
4
|
import { formatFieldLabel } from './helpers'
|
|
5
5
|
|
|
6
|
-
export type ResolvedField = { key: string; label: string; multiline: boolean }
|
|
6
|
+
export type ResolvedField = { key: string; label: string; multiline: boolean; baseValue?: string }
|
|
7
7
|
|
|
8
8
|
function isMultiline(key: string): boolean {
|
|
9
9
|
return key === 'description' || key.includes('description') || key.includes('content')
|
|
@@ -13,6 +13,7 @@ export function resolveFieldList(
|
|
|
13
13
|
entityType: string,
|
|
14
14
|
explicitFields: string[] | undefined,
|
|
15
15
|
customFieldDefs: Array<{ key: string; kind: string; label?: string }>,
|
|
16
|
+
baseValues?: Record<string, unknown> | null,
|
|
16
17
|
): ResolvedField[] {
|
|
17
18
|
if (explicitFields?.length) {
|
|
18
19
|
return explicitFields.map((key) => ({
|
|
@@ -56,6 +57,19 @@ export function resolveFieldList(
|
|
|
56
57
|
}
|
|
57
58
|
}
|
|
58
59
|
|
|
60
|
+
const expander = getTranslatableFieldExpander(entityType)
|
|
61
|
+
if (expander && baseValues) {
|
|
62
|
+
for (const expansion of expander(baseValues)) {
|
|
63
|
+
if (fields.some((f) => f.key === expansion.key)) continue
|
|
64
|
+
fields.push({
|
|
65
|
+
key: expansion.key,
|
|
66
|
+
label: expansion.label ?? formatFieldLabel(expansion.key),
|
|
67
|
+
multiline: isMultiline(expansion.key),
|
|
68
|
+
...(expansion.baseValue !== undefined ? { baseValue: expansion.baseValue } : {}),
|
|
69
|
+
})
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
|
|
59
73
|
if (hasExplicitList) return fields
|
|
60
74
|
|
|
61
75
|
for (const def of customFieldDefs) {
|