@mohasinac/appkit 4.53.0 โ 4.53.2
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/dist/.build-stamp.json +3 -3
- package/dist/_internal/client/scaffolds/AppShell.js +1 -1
- package/dist/_internal/client/theme/ThemeProvider.js +15 -1
- package/dist/_internal/server/features/auctions/og.d.ts +3 -1
- package/dist/_internal/server/features/auctions/og.js +3 -1
- package/dist/_internal/server/features/bundles/actions.js +1 -1
- package/dist/_internal/server/features/business-documents/download.d.ts +41 -0
- package/dist/_internal/server/features/business-documents/download.js +91 -0
- package/dist/_internal/server/features/categories/adapters.d.ts +1 -1
- package/dist/_internal/server/features/categories/adapters.js +10 -0
- package/dist/_internal/server/features/categories/price-guidance-resolve.d.ts +66 -0
- package/dist/_internal/server/features/categories/price-guidance-resolve.js +138 -0
- package/dist/_internal/server/features/checkout/actions.d.ts +14 -0
- package/dist/_internal/server/features/checkout/actions.js +106 -12
- package/dist/_internal/server/features/checkout/bundle-expansion.js +17 -0
- package/dist/_internal/server/features/checkout/data.d.ts +22 -0
- package/dist/_internal/server/features/checkout/group-rule-revalidation.d.ts +34 -0
- package/dist/_internal/server/features/checkout/group-rule-revalidation.js +124 -0
- package/dist/_internal/server/features/checkout/supplier-identity.d.ts +55 -0
- package/dist/_internal/server/features/checkout/supplier-identity.js +81 -0
- package/dist/_internal/server/features/classified/metadata.js +1 -1
- package/dist/_internal/server/features/classified/og.d.ts +3 -1
- package/dist/_internal/server/features/classified/og.js +3 -1
- package/dist/_internal/server/features/digital-code/metadata.js +1 -1
- package/dist/_internal/server/features/digital-code/og.d.ts +3 -1
- package/dist/_internal/server/features/digital-code/og.js +3 -1
- package/dist/_internal/server/features/digital-code/pool.d.ts +19 -4
- package/dist/_internal/server/features/digital-code/pool.js +19 -4
- package/dist/_internal/server/features/live/metadata.js +1 -1
- package/dist/_internal/server/features/live/og.d.ts +3 -1
- package/dist/_internal/server/features/live/og.js +3 -1
- package/dist/_internal/server/features/messages/actions.js +1 -1
- package/dist/_internal/server/features/parts/og.js +1 -1
- package/dist/_internal/server/features/pre-orders/og.d.ts +3 -1
- package/dist/_internal/server/features/pre-orders/og.js +3 -1
- package/dist/_internal/server/features/price-index/market.d.ts +57 -0
- package/dist/_internal/server/features/price-index/market.js +120 -0
- package/dist/_internal/server/features/price-index/search.d.ts +18 -0
- package/dist/_internal/server/features/price-index/search.js +47 -7
- package/dist/_internal/server/features/prize-draws/og.d.ts +3 -1
- package/dist/_internal/server/features/prize-draws/og.js +3 -1
- package/dist/_internal/server/features/products/group-members.d.ts +26 -0
- package/dist/_internal/server/features/products/group-members.js +7 -1
- package/dist/_internal/server/features/products/list-public.d.ts +12 -0
- package/dist/_internal/server/features/products/list-public.js +10 -1
- package/dist/_internal/server/features/products/listing-tab-counts.d.ts +25 -1
- package/dist/_internal/server/features/products/listing-tab-counts.js +128 -10
- package/dist/_internal/server/features/products/og.d.ts +3 -1
- package/dist/_internal/server/features/products/og.js +3 -1
- package/dist/_internal/server/features/search/scoped-sources.d.ts +26 -0
- package/dist/_internal/server/features/search/scoped-sources.js +252 -0
- package/dist/_internal/server/features/search/search-everything.d.ts +57 -0
- package/dist/_internal/server/features/search/search-everything.js +70 -20
- package/dist/_internal/server/features/seo/sitemap.js +6 -0
- package/dist/_internal/server/features/shared/listing-adapters.js +4 -2
- package/dist/_internal/server/features/sticker-trace/adapters.d.ts +11 -0
- package/dist/_internal/server/features/sticker-trace/adapters.js +1 -0
- package/dist/_internal/server/features/sticker-trace/promote-to-listings.d.ts +26 -0
- package/dist/_internal/server/features/sticker-trace/promote-to-listings.js +264 -0
- package/dist/_internal/server/features/sticker-trace/sheet-actions.d.ts +3 -1
- package/dist/_internal/server/features/sticker-trace/sheet-actions.js +32 -0
- package/dist/_internal/server/features/sticker-trace/storage.d.ts +20 -1
- package/dist/_internal/server/features/sticker-trace/storage.js +20 -1
- package/dist/_internal/server/features/stores/adapters.d.ts +1 -1
- package/dist/_internal/server/features/stores/adapters.js +16 -0
- package/dist/_internal/server/features/stores/index.d.ts +1 -0
- package/dist/_internal/server/features/stores/index.js +1 -0
- package/dist/_internal/server/features/stores/tabs.d.ts +30 -0
- package/dist/_internal/server/features/stores/tabs.js +124 -0
- package/dist/_internal/shared/checkout/rules/_defaults.js +0 -2
- package/dist/_internal/shared/checkout/rules/_limits.d.ts +14 -1
- package/dist/_internal/shared/checkout/rules/_limits.js +14 -1
- package/dist/_internal/shared/checkout/rules/_registry.js +2 -0
- package/dist/_internal/shared/checkout/rules/auction.rule.js +9 -1
- package/dist/_internal/shared/checkout/rules/bundle.rule.js +13 -0
- package/dist/_internal/shared/checkout/rules/index.d.ts +2 -0
- package/dist/_internal/shared/checkout/rules/index.js +9 -0
- package/dist/_internal/shared/checkout/rules/offer.rule.js +0 -1
- package/dist/_internal/shared/checkout/rules/payment-gate.d.ts +26 -0
- package/dist/_internal/shared/checkout/rules/payment-gate.js +133 -0
- package/dist/_internal/shared/checkout/rules/prize-draw.rule.js +29 -8
- package/dist/_internal/shared/checkout/rules/spares.rule.d.ts +16 -0
- package/dist/_internal/shared/checkout/rules/spares.rule.js +5 -0
- package/dist/_internal/shared/checkout/rules/types.d.ts +26 -2
- package/dist/_internal/shared/config/schema.d.ts +122 -0
- package/dist/_internal/shared/features/cart/schema.js +3 -2
- package/dist/_internal/shared/features/products/group-rules.d.ts +75 -0
- package/dist/_internal/shared/features/products/group-rules.js +142 -0
- package/dist/_internal/shared/listing-types/_registry.d.ts +24 -0
- package/dist/_internal/shared/listing-types/_registry.js +39 -1
- package/dist/_internal/shared/listing-types/art/config.d.ts +1 -0
- package/dist/_internal/shared/listing-types/art/config.js +2 -0
- package/dist/_internal/shared/listing-types/auction/config.d.ts +1 -0
- package/dist/_internal/shared/listing-types/auction/config.js +2 -0
- package/dist/_internal/shared/listing-types/availability.js +48 -0
- package/dist/_internal/shared/listing-types/capabilities.js +19 -0
- package/dist/_internal/shared/listing-types/classified/config.d.ts +1 -0
- package/dist/_internal/shared/listing-types/classified/config.js +2 -0
- package/dist/_internal/shared/listing-types/digital-code/config.d.ts +1 -0
- package/dist/_internal/shared/listing-types/digital-code/config.js +2 -0
- package/dist/_internal/shared/listing-types/feature-flags.js +1 -0
- package/dist/_internal/shared/listing-types/live/config.d.ts +1 -0
- package/dist/_internal/shared/listing-types/live/config.js +2 -0
- package/dist/_internal/shared/listing-types/pre-order/config.d.ts +1 -0
- package/dist/_internal/shared/listing-types/pre-order/config.js +2 -0
- package/dist/_internal/shared/listing-types/prize-draw/config.d.ts +1 -0
- package/dist/_internal/shared/listing-types/prize-draw/config.js +2 -0
- package/dist/_internal/shared/listing-types/spares/config.d.ts +74 -0
- package/dist/_internal/shared/listing-types/spares/config.js +60 -0
- package/dist/_internal/shared/listing-types/spares/ctas.d.ts +1 -0
- package/dist/_internal/shared/listing-types/spares/ctas.js +4 -0
- package/dist/_internal/shared/listing-types/spares/og.js +1 -0
- package/dist/_internal/shared/listing-types/spares/schema.d.ts +2 -0
- package/dist/_internal/shared/listing-types/spares/schema.js +3 -0
- package/dist/_internal/shared/listing-types/spares/seed-factory.d.ts +1 -0
- package/dist/_internal/shared/listing-types/spares/seed-factory.js +1 -0
- package/dist/_internal/shared/listing-types/standard/config.d.ts +1 -0
- package/dist/_internal/shared/listing-types/standard/config.js +2 -0
- package/dist/_internal/shared/listing-types/stickers/config.d.ts +1 -0
- package/dist/_internal/shared/listing-types/stickers/config.js +2 -0
- package/dist/_internal/shared/search/match.d.ts +11 -0
- package/dist/_internal/shared/search/match.js +30 -3
- package/dist/_internal/shared/search/to-groups.d.ts +52 -0
- package/dist/_internal/shared/search/to-groups.js +141 -0
- package/dist/client.d.ts +8 -2
- package/dist/client.js +4 -1
- package/dist/constants/api-endpoints.d.ts +46 -15
- package/dist/constants/api-endpoints.js +35 -5
- package/dist/constants/cache-invalidation.js +3 -1
- package/dist/constants/field-names.d.ts +6 -0
- package/dist/constants/field-names.js +6 -0
- package/dist/features/about/components/PolicyPageView.d.ts +22 -2
- package/dist/features/about/components/PolicyPageView.js +14 -2
- package/dist/features/about/components/PublicProfileView.js +2 -1
- package/dist/features/about/components/ShippingPolicyView.js +42 -2
- package/dist/features/account/components/NotificationBell.js +1 -1
- package/dist/features/account/hooks/useProfileStats.js +5 -1
- package/dist/features/addresses/repository/addresses.repository.d.ts +16 -0
- package/dist/features/addresses/repository/addresses.repository.js +21 -0
- package/dist/features/admin/actions/admin-actions.js +16 -1
- package/dist/features/admin/components/AdminCategoryEditorView.js +19 -2
- package/dist/features/admin/components/AdminNavigationView.js +1 -1
- package/dist/features/admin/components/AdminPageHeader.js +1 -1
- package/dist/features/admin/components/AdminProductEditorView.js +17 -2
- package/dist/features/admin/components/AdminSidebar.js +1 -1
- package/dist/features/admin/components/AdminSiteSettingsView.js +39 -8
- package/dist/features/admin/components/AdminSparesView.d.ts +12 -0
- package/dist/features/admin/components/AdminSparesView.js +30 -0
- package/dist/features/admin/components/AdminUsersView.js +9 -7
- package/dist/features/admin/components/index.d.ts +2 -0
- package/dist/features/admin/components/index.js +1 -0
- package/dist/features/admin/components/sections/adminSectionsTypes.js +7 -0
- package/dist/features/admin/constants/filter-tabs.d.ts +3 -0
- package/dist/features/admin/constants/filter-tabs.js +1 -0
- package/dist/features/admin/schemas/firestore.d.ts +26 -2
- package/dist/features/auctions/components/AuctionDetailPageView.js +12 -18
- package/dist/features/auth/repository/user.repository.d.ts +13 -0
- package/dist/features/auth/repository/user.repository.js +28 -0
- package/dist/features/auth/schemas/firestore.d.ts +2 -0
- package/dist/features/auth/schemas/firestore.js +2 -0
- package/dist/features/business-documents/repository/business-documents.repository.d.ts +58 -0
- package/dist/features/business-documents/repository/business-documents.repository.js +54 -0
- package/dist/features/business-documents/schemas/firestore.d.ts +105 -0
- package/dist/features/business-documents/schemas/firestore.js +75 -0
- package/dist/features/cart/actions/cart-actions.js +83 -6
- package/dist/features/cart/repository/cart.repository.js +32 -4
- package/dist/features/cart/schemas/firestore.d.ts +20 -0
- package/dist/features/cart/schemas/index.d.ts +2 -2
- package/dist/features/catalogue/components/PublicCatalogueItemDetailView.js +1 -1
- package/dist/features/categories/api/[id]/price-guidance/route.d.ts +42 -0
- package/dist/features/categories/api/[id]/price-guidance/route.js +90 -0
- package/dist/features/categories/components/BrandDetailPageView.js +32 -8
- package/dist/features/categories/components/BrandDetailTabs.d.ts +7 -1
- package/dist/features/categories/components/BrandDetailTabs.js +28 -3
- package/dist/features/categories/components/BundleCollage.js +3 -2
- package/dist/features/categories/components/BundleItemsPicker.js +3 -4
- package/dist/features/categories/components/CategoriesIndexListing.js +23 -8
- package/dist/features/categories/components/CategoryDetailPageView.js +157 -20
- package/dist/features/categories/components/CategoryDetailTabs.d.ts +7 -1
- package/dist/features/categories/components/CategoryDetailTabs.js +41 -7
- package/dist/features/categories/components/CategoryGrid.js +10 -1
- package/dist/features/categories/components/PriceGuidanceHint.d.ts +29 -0
- package/dist/features/categories/components/PriceGuidanceHint.js +61 -0
- package/dist/features/categories/components/SubcategoryCardRow.d.ts +27 -0
- package/dist/features/categories/components/SubcategoryCardRow.js +98 -0
- package/dist/features/categories/constants/category-tabs.d.ts +20 -25
- package/dist/features/categories/constants/category-tabs.js +22 -39
- package/dist/features/categories/repository/categories.repository.d.ts +12 -0
- package/dist/features/categories/repository/categories.repository.js +20 -0
- package/dist/features/categories/schemas/firestore.d.ts +22 -1
- package/dist/features/categories/schemas/firestore.js +10 -0
- package/dist/features/categories/schemas/index.d.ts +6 -0
- package/dist/features/categories/schemas/index.js +2 -0
- package/dist/features/categories/types/index.d.ts +8 -0
- package/dist/features/categories/utils/taxonomy-visual.d.ts +16 -3
- package/dist/features/categories/utils/taxonomy-visual.js +26 -10
- package/dist/features/classified/components/ClassifiedDetailPageView.js +14 -10
- package/dist/features/contact/components/ContactPageView.d.ts +33 -1
- package/dist/features/contact/components/ContactPageView.js +58 -28
- package/dist/features/digital-codes/components/DigitalCodeDetailPageView.js +14 -10
- package/dist/features/grouped/components/GroupedListingDetailView.js +9 -1
- package/dist/features/grouped/schemas/firestore.d.ts +5 -0
- package/dist/features/homepage/actions/homepage-section-actions.d.ts +3 -3
- package/dist/features/homepage/components/CollectionCardsSection.d.ts +28 -6
- package/dist/features/homepage/components/CollectionCardsSection.js +43 -8
- package/dist/features/homepage/components/HowItWorksSection.d.ts +23 -6
- package/dist/features/homepage/components/HowItWorksSection.js +4 -4
- package/dist/features/homepage/components/PromoGrid.d.ts +31 -2
- package/dist/features/homepage/components/PromoGrid.js +12 -3
- package/dist/features/homepage/components/TestimonialsCarousel.d.ts +34 -2
- package/dist/features/homepage/components/TestimonialsCarousel.js +1 -1
- package/dist/features/homepage/components/index.d.ts +0 -20
- package/dist/features/homepage/components/index.js +0 -11
- package/dist/features/homepage/index.d.ts +0 -2
- package/dist/features/homepage/index.js +0 -2
- package/dist/features/homepage/lib/section-renderer.d.ts +9 -0
- package/dist/features/homepage/lib/section-renderer.js +202 -58
- package/dist/features/homepage/repository/homepage-sections.repository.d.ts +35 -3
- package/dist/features/homepage/repository/homepage-sections.repository.js +58 -8
- package/dist/features/homepage/schemas/firestore.d.ts +106 -3
- package/dist/features/homepage/schemas/firestore.js +32 -2
- package/dist/features/homepage/schemas/index.d.ts +36 -7
- package/dist/features/homepage/schemas/index.js +39 -27
- package/dist/features/homepage/server.d.ts +0 -3
- package/dist/features/homepage/server.js +0 -3
- package/dist/features/homepage/types/index.d.ts +0 -47
- package/dist/features/homepage/types/index.js +49 -0
- package/dist/features/layout/AppLayoutShell.js +1 -1
- package/dist/features/layout/BottomNavItem.js +1 -1
- package/dist/features/layout/BottomSheet.js +1 -1
- package/dist/features/layout/TitleBarLayout.js +1 -1
- package/dist/features/live/components/LiveItemDetailPageView.js +14 -10
- package/dist/features/media/hooks/useMedia.js +17 -0
- package/dist/features/media/repository/media-assets.repository.d.ts +46 -0
- package/dist/features/media/repository/media-assets.repository.js +31 -0
- package/dist/features/media/trust-signals.d.ts +85 -0
- package/dist/features/media/trust-signals.js +126 -0
- package/dist/features/parts/components/PartDetailPageView.d.ts +14 -1
- package/dist/features/parts/components/PartDetailPageView.js +2 -2
- package/dist/features/parts/components/PartsIndexPageView.d.ts +11 -2
- package/dist/features/parts/components/PartsIndexPageView.js +2 -2
- package/dist/features/parts/components/PartsSearchBox.d.ts +9 -0
- package/dist/features/parts/components/PartsSearchBox.js +57 -0
- package/dist/features/parts/schemas/firestore.d.ts +149 -3
- package/dist/features/parts/schemas/firestore.js +184 -0
- package/dist/features/payments/repository/payout.repository.d.ts +15 -0
- package/dist/features/payments/repository/payout.repository.js +21 -0
- package/dist/features/pre-orders/components/PreOrderDetailPageView.js +12 -18
- package/dist/features/pre-orders/components/PreOrdersIndexListing.js +1 -1
- package/dist/features/price-index/components/PriceIndexToolView.d.ts +24 -1
- package/dist/features/price-index/components/PriceIndexToolView.js +299 -17
- package/dist/features/price-index/lookup-target.d.ts +48 -0
- package/dist/features/price-index/lookup-target.js +107 -0
- package/dist/features/products/components/AvailabilityScopeHint.d.ts +22 -0
- package/dist/features/products/components/AvailabilityScopeHint.js +27 -0
- package/dist/features/products/components/CompareOverlay.d.ts +20 -2
- package/dist/features/products/components/CompareOverlay.js +23 -3
- package/dist/features/products/components/CustomSectionTabContent.d.ts +31 -0
- package/dist/features/products/components/CustomSectionTabContent.js +29 -3
- package/dist/features/products/components/GroupMemberPicker.d.ts +19 -1
- package/dist/features/products/components/GroupMemberPicker.js +46 -8
- package/dist/features/products/components/GroupSettingsPanel.d.ts +11 -1
- package/dist/features/products/components/GroupSettingsPanel.js +92 -3
- package/dist/features/products/components/PrizeDrawDetailPageView.js +1 -1
- package/dist/features/products/components/PrizeDrawEntryActions.d.ts +15 -1
- package/dist/features/products/components/PrizeDrawEntryActions.js +70 -8
- package/dist/features/products/components/ProductDetailPageView.js +81 -17
- package/dist/features/products/components/ProductForm.d.ts +13 -1
- package/dist/features/products/components/ProductForm.js +16 -2
- package/dist/features/products/components/ProductsIndexListing.d.ts +16 -1
- package/dist/features/products/components/ProductsIndexListing.js +3 -3
- package/dist/features/products/components/ShowGroupSection.js +15 -2
- package/dist/features/products/components/SparesListView.d.ts +7 -0
- package/dist/features/products/components/SparesListView.js +39 -0
- package/dist/features/products/components/SublistingCarouselSection.js +2 -1
- package/dist/features/products/constants/action-defs.d.ts +1 -0
- package/dist/features/products/constants/action-defs.js +1 -0
- package/dist/features/products/repository/products.repository.d.ts +99 -0
- package/dist/features/products/repository/products.repository.js +240 -18
- package/dist/features/products/schemas/firestore.d.ts +97 -4
- package/dist/features/products/schemas/firestore.js +104 -0
- package/dist/features/products/schemas/index.d.ts +10 -19
- package/dist/features/products/schemas/index.js +23 -6
- package/dist/features/products/types/index.d.ts +45 -1
- package/dist/features/products/utils/group-member-image.d.ts +46 -0
- package/dist/features/products/utils/group-member-image.js +4 -0
- package/dist/features/products/utils/listing-badge-variant.js +4 -0
- package/dist/features/reviews/components/ReviewsList.js +1 -1
- package/dist/features/scams/components/ScamProfileView.js +1 -1
- package/dist/features/search/components/EntityTypeahead.d.ts +44 -0
- package/dist/features/search/components/EntityTypeahead.js +92 -0
- package/dist/features/search/components/Search.d.ts +12 -1
- package/dist/features/search/components/Search.js +47 -3
- package/dist/features/search/components/SearchFacetRail.d.ts +11 -0
- package/dist/features/search/components/SearchFacetRail.js +92 -0
- package/dist/features/search/components/SearchResultsClient.d.ts +33 -0
- package/dist/features/search/components/SearchResultsClient.js +103 -0
- package/dist/features/search/components/SearchResultsPageView.d.ts +15 -1
- package/dist/features/search/components/SearchResultsPageView.js +2 -2
- package/dist/features/search/components/index.d.ts +0 -6
- package/dist/features/search/components/index.js +0 -3
- package/dist/features/search/hooks/useNavSuggestions.d.ts +13 -0
- package/dist/features/search/hooks/useScopedSearch.d.ts +34 -0
- package/dist/features/search/hooks/useScopedSearch.js +114 -0
- package/dist/features/search/hooks/useSiteSearch.d.ts +26 -0
- package/dist/features/search/hooks/useSiteSearch.js +149 -0
- package/dist/features/search/index.d.ts +0 -4
- package/dist/features/search/index.js +0 -4
- package/dist/features/search/manifest.d.ts +12 -0
- package/dist/features/search/manifest.js +17 -2
- package/dist/features/search/server.d.ts +0 -3
- package/dist/features/search/server.js +0 -3
- package/dist/features/seller/components/SellerListingTypeView.d.ts +1 -1
- package/dist/features/seller/components/SellerListingTypeView.js +20 -0
- package/dist/features/seller/components/SellerPayoutSettingsView.js +35 -14
- package/dist/features/seller/components/SellerProductShell.d.ts +1 -1
- package/dist/features/seller/components/SellerProductShell.js +2 -1
- package/dist/features/seller/components/SellerProductsView.js +3 -2
- package/dist/features/seller/components/SellerSidebar.js +1 -1
- package/dist/features/seller/components/SellerSparesView.d.ts +17 -0
- package/dist/features/seller/components/SellerSparesView.js +6 -0
- package/dist/features/seller/components/index.d.ts +2 -0
- package/dist/features/seller/components/index.js +1 -0
- package/dist/features/shell/CommandPalette.js +1 -1
- package/dist/features/sticker-trace/schemas/firestore.d.ts +19 -0
- package/dist/features/stores/components/StoreAuctionsListing.js +2 -1
- package/dist/features/stores/components/StoreDetailLayoutView.d.ts +1 -1
- package/dist/features/stores/components/StoreDetailLayoutView.js +7 -82
- package/dist/features/stores/components/StoreHeader.js +1 -2
- package/dist/features/stores/components/StoreSparesPageView.d.ts +27 -0
- package/dist/features/stores/components/StoreSparesPageView.js +48 -0
- package/dist/features/stores/schemas/firestore.d.ts +19 -0
- package/dist/features/tester/seed-data/authored/buying__spares-and-groups.d.ts +2 -0
- package/dist/features/tester/seed-data/authored/buying__spares-and-groups.js +96 -0
- package/dist/features/tester/seed-data/authored/content-discovery__authenticity-checker.d.ts +2 -0
- package/dist/features/tester/seed-data/authored/content-discovery__authenticity-checker.js +392 -45
- package/dist/features/tester/seed-data/authored/content-discovery__category-tabs-franchise-bundles.js +54 -32
- package/dist/features/tester/seed-data/authored/content-discovery__parts-database.js +3 -2
- package/dist/features/tester/seed-data/authored/content-discovery__price-index-and-compare.d.ts +2 -0
- package/dist/features/tester/seed-data/authored/content-discovery__price-index-and-compare.js +73 -0
- package/dist/features/tester/seed-data/authored/content-discovery__site-wide-index.d.ts +2 -0
- package/dist/features/tester/seed-data/authored/content-discovery__site-wide-index.js +340 -0
- package/dist/features/tester/seed-data/authored/index.js +16 -0
- package/dist/features/tester/seed-data/authored/search-and-nav__header-search.d.ts +1 -0
- package/dist/features/tester/seed-data/authored/search-and-nav__header-search.js +57 -0
- package/dist/features/tester/seed-data/authored/selling__spares-and-group-editing.d.ts +2 -0
- package/dist/features/tester/seed-data/authored/selling__spares-and-group-editing.js +77 -0
- package/dist/features/tester/seed-data/authored/tools__sticker-promote-to-listings.d.ts +2 -0
- package/dist/features/tester/seed-data/authored/tools__sticker-promote-to-listings.js +58 -0
- package/dist/features/tester/seed-data/authored/tools__sticker-tracer.js +5 -4
- package/dist/features/tester/seed-data/tester-checklist-seed-data.js +384 -18
- package/dist/index.d.ts +17 -57
- package/dist/index.js +35 -96
- package/dist/next/routing/route-map.d.ts +42 -1
- package/dist/next/routing/route-map.js +45 -0
- package/dist/providers/db-firebase/base.repository.d.ts +22 -0
- package/dist/providers/db-firebase/base.repository.js +36 -0
- package/dist/schemas/registry.d.ts +15 -6
- package/dist/security/blind-index-tokens.d.ts +54 -0
- package/dist/security/blind-index-tokens.js +152 -0
- package/dist/security/index.d.ts +1 -1
- package/dist/security/index.js +1 -1
- package/dist/security/settings-encryption.d.ts +35 -0
- package/dist/security/settings-encryption.js +91 -0
- package/dist/seed/_helpers/brand-rows.js +38 -14
- package/dist/seed/_helpers/category-forest.js +73 -7
- package/dist/seed/_helpers/category-images-generated.d.ts +25 -6
- package/dist/seed/_helpers/category-images-generated.js +81 -6
- package/dist/seed/_helpers/category-models.d.ts +1 -1
- package/dist/seed/_helpers/category-models.js +7 -1
- package/dist/seed/_helpers/category-parts-glossary-map.js +8 -0
- package/dist/seed/_helpers/category-parts.d.ts +1 -1
- package/dist/seed/_helpers/category-parts.js +50 -2
- package/dist/seed/_helpers/character-features.d.ts +1 -1
- package/dist/seed/_helpers/character-features.js +2 -1
- package/dist/seed/_helpers/franchise-rows.js +10 -2
- package/dist/seed/_helpers/media.d.ts +47 -0
- package/dist/seed/_helpers/media.js +34 -0
- package/dist/seed/authenticity-knowledge-seed-data.d.ts +49 -5
- package/dist/seed/authenticity-knowledge-seed-data.js +10001 -549
- package/dist/seed/categories-seed-data.js +124 -20
- package/dist/seed/grouped-listings-seed-data.js +16 -0
- package/dist/seed/homepage-sections-seed-data.js +94 -5
- package/dist/seed/index.d.ts +2 -3
- package/dist/seed/index.js +2 -2
- package/dist/seed/manifest.js +3 -0
- package/dist/seed/media-assets-seed-data.d.ts +1 -1
- package/dist/seed/media-assets-seed-data.js +746 -196
- package/dist/seed/model-composition-seed-data.js +122 -6
- package/dist/seed/part-glossary-seed-data.d.ts +1 -1
- package/dist/seed/part-glossary-seed-data.js +37219 -463
- package/dist/seed/products-art-seed-data.js +7 -18
- package/dist/seed/products-auctions-seed-data.js +56 -47
- package/dist/seed/products-beyblade-x-seed-data.d.ts +2 -0
- package/dist/seed/products-beyblade-x-seed-data.js +476 -0
- package/dist/seed/products-catalogue-seed-data.d.ts +5 -5
- package/dist/seed/products-catalogue-seed-data.js +1500 -1240
- package/dist/seed/products-classifieds-seed-data.js +9 -27
- package/dist/seed/products-digital-codes-seed-data.js +9 -18
- package/dist/seed/products-live-items-seed-data.js +5 -13
- package/dist/seed/products-preorders-seed-data.js +67 -33
- package/dist/seed/products-prize-draws-seed-data.js +8 -17
- package/dist/seed/products-spares-seed-data.d.ts +2 -0
- package/dist/seed/products-spares-seed-data.js +368 -0
- package/dist/seed/products-standard-seed-data.js +39 -126
- package/dist/seed/products-stickers-seed-data.js +59 -15
- package/dist/seed/site-settings-seed-data.js +8 -0
- package/dist/seo/json-ld.d.ts +25 -0
- package/dist/seo/json-ld.js +28 -0
- package/dist/server-entry.d.ts +3 -1
- package/dist/server-entry.js +2 -2
- package/dist/server.d.ts +11 -9
- package/dist/server.js +35 -25
- package/dist/styles.css +202 -1
- package/dist/tailwind-input.css +31 -17
- package/dist/tailwind-utilities.css +1 -1
- package/dist/ui/components/Accordion.js +1 -1
- package/dist/ui/components/Breadcrumb.js +1 -1
- package/dist/ui/components/DetailPageGallery.js +0 -2
- package/dist/ui/components/DetailPageHero.js +0 -1
- package/dist/ui/components/DetailPageTabs.js +1 -1
- package/dist/ui/components/Divider.js +3 -3
- package/dist/ui/components/GatedPrice.js +5 -1
- package/dist/ui/components/HorizontalScroller.js +1 -1
- package/dist/ui/components/ListingLayout.js +3 -3
- package/dist/ui/components/MediaTabs.style.css +81 -0
- package/dist/ui/components/Pagination.js +1 -1
- package/dist/ui/components/PendingValue.d.ts +45 -0
- package/dist/ui/components/PendingValue.js +7 -0
- package/dist/ui/components/Radio.style.css +119 -0
- package/dist/ui/components/RatingDisplay.js +2 -2
- package/dist/ui/components/SettingsSection.js +0 -2
- package/dist/ui/components/SiteLogo.js +3 -0
- package/dist/ui/components/SiteMark.js +3 -0
- package/dist/ui/components/SortDropdown.js +1 -1
- package/dist/ui/components/SpecTable.d.ts +58 -0
- package/dist/ui/components/SpecTable.js +8 -0
- package/dist/ui/components/StarRating.js +1 -3
- package/dist/ui/components/SummaryCard.js +1 -1
- package/dist/ui/components/index.style.css +1 -0
- package/dist/ui/icons/icon-registry.d.ts +1 -0
- package/dist/ui/icons/icon-registry.js +9 -1
- package/dist/ui/index.d.ts +4 -0
- package/dist/ui/index.js +2 -0
- package/dist/utils/search-txt-query.d.ts +12 -0
- package/dist/utils/search-txt-query.js +12 -0
- package/package.json +1 -1
- package/scripts/dedupe-peer-deps.mjs +18 -0
- package/scripts/seed-cli.mjs +250 -35
- package/scripts/verify-css-build.mjs +118 -37
- package/dist/_internal/server/features/search/actions.d.ts +0 -7
- package/dist/_internal/server/features/search/actions.js +0 -21
- package/dist/_internal/server/features/search/data.d.ts +0 -15
- package/dist/_internal/server/features/search/data.js +0 -18
- package/dist/_internal/server/features/search/index.d.ts +0 -3
- package/dist/_internal/server/features/search/index.js +0 -2
- package/dist/features/homepage/api/[id]/route.d.ts +0 -23
- package/dist/features/homepage/api/[id]/route.js +0 -103
- package/dist/features/homepage/api/route.d.ts +0 -14
- package/dist/features/homepage/api/route.js +0 -140
- package/dist/features/homepage/api/sections/[id]/route.d.ts +0 -26
- package/dist/features/homepage/api/sections/[id]/route.js +0 -95
- package/dist/features/homepage/api/sections/route.d.ts +0 -13
- package/dist/features/homepage/api/sections/route.js +0 -82
- package/dist/features/homepage/components/AdvertisementBanner.d.ts +0 -14
- package/dist/features/homepage/components/AdvertisementBanner.js +0 -35
- package/dist/features/homepage/components/BeforeAfterCard.d.ts +0 -9
- package/dist/features/homepage/components/BeforeAfterCard.js +0 -73
- package/dist/features/homepage/components/FeaturedResultsSection.d.ts +0 -17
- package/dist/features/homepage/components/FeaturedResultsSection.js +0 -17
- package/dist/features/homepage/components/HeroBanner.d.ts +0 -7
- package/dist/features/homepage/components/HeroBanner.js +0 -43
- package/dist/features/homepage/components/HeroSection.d.ts +0 -7
- package/dist/features/homepage/components/HeroSection.js +0 -6
- package/dist/features/homepage/components/HomepageSkeleton.d.ts +0 -2
- package/dist/features/homepage/components/HomepageSkeleton.js +0 -21
- package/dist/features/homepage/components/HomepageView.d.ts +0 -25
- package/dist/features/homepage/components/HomepageView.js +0 -5
- package/dist/features/homepage/components/NewsletterBanner.d.ts +0 -23
- package/dist/features/homepage/components/NewsletterBanner.js +0 -37
- package/dist/features/homepage/components/SiteFeaturesSection.d.ts +0 -14
- package/dist/features/homepage/components/SiteFeaturesSection.js +0 -11
- package/dist/features/homepage/components/TrustBadges.d.ts +0 -6
- package/dist/features/homepage/components/TrustBadges.js +0 -46
- package/dist/features/homepage/components/TrustIndicatorsSection.d.ts +0 -12
- package/dist/features/homepage/components/TrustIndicatorsSection.js +0 -11
- package/dist/features/homepage/hooks/useHomepage.d.ts +0 -11
- package/dist/features/homepage/hooks/useHomepage.js +0 -17
- package/dist/features/homepage/hooks/useHomepageSections.d.ts +0 -2
- package/dist/features/homepage/hooks/useHomepageSections.js +0 -10
- package/dist/features/homepage/repository/homepage.repository.d.ts +0 -11
- package/dist/features/homepage/repository/homepage.repository.js +0 -25
- package/dist/features/search/actions/index.d.ts +0 -1
- package/dist/features/search/actions/index.js +0 -1
- package/dist/features/search/actions/search-actions.d.ts +0 -18
- package/dist/features/search/actions/search-actions.js +0 -43
- package/dist/features/search/api/route.d.ts +0 -22
- package/dist/features/search/api/route.js +0 -128
- package/dist/features/search/columns/index.d.ts +0 -17
- package/dist/features/search/columns/index.js +0 -49
- package/dist/features/search/components/SearchFiltersRow.d.ts +0 -22
- package/dist/features/search/components/SearchFiltersRow.js +0 -21
- package/dist/features/search/components/SearchResultsSection.d.ts +0 -53
- package/dist/features/search/components/SearchResultsSection.js +0 -35
- package/dist/features/search/components/SearchView.d.ts +0 -27
- package/dist/features/search/components/SearchView.js +0 -8
- package/dist/features/search/hooks/useSearch.d.ts +0 -21
- package/dist/features/search/hooks/useSearch.js +0 -33
- package/dist/features/search/repository/search.repository.d.ts +0 -14
- package/dist/features/search/repository/search.repository.js +0 -59
- package/dist/features/search/schemas/index.d.ts +0 -70
- package/dist/features/search/schemas/index.js +0 -36
- package/dist/features/search/types/index.d.ts +0 -48
- package/dist/features/stores/components/StoreScopedSearch.d.ts +0 -17
- package/dist/features/stores/components/StoreScopedSearch.js +0 -27
- package/dist/seed/defaults/homepage-sections.d.ts +0 -2
- package/dist/seed/defaults/homepage-sections.js +0 -60
- package/dist/seed/factories/homepage-section.factory.d.ts +0 -10
- package/dist/seed/factories/homepage-section.factory.js +0 -13
- /package/dist/{features/search/types/index.js โ _internal/shared/listing-types/spares/og.d.ts} +0 -0
|
@@ -11,9 +11,10 @@
|
|
|
11
11
|
* the tab bar renders, so a tenth listing type is counted automatically.
|
|
12
12
|
*
|
|
13
13
|
* EXPORTS:
|
|
14
|
-
* ListingTabScope
|
|
15
|
-
* ListingTabCounts
|
|
16
|
-
*
|
|
14
|
+
* ListingTabScope โ the surface's scope (category / brand / store)
|
|
15
|
+
* ListingTabCounts โ tabSlug -> count, `undefined` meaning "unknown"
|
|
16
|
+
* ListingTabCountResult โ the AVAILABLE and TOTAL maps, see below
|
|
17
|
+
* listingTabCounts โ resolve every tab's counts in one parallel batch
|
|
17
18
|
*
|
|
18
19
|
* @tag domain:products,categories,stores
|
|
19
20
|
* @tag layer:server
|
|
@@ -25,6 +26,11 @@
|
|
|
25
26
|
import { categoriesRepository, productRepository } from "../../../../repositories/index.js";
|
|
26
27
|
import { serverLogger } from "../../../../monitoring/index.js";
|
|
27
28
|
import { normalizeError } from "../../../../errors/normalize.js";
|
|
29
|
+
import { sieveAnd, sieveFilter, SIEVE_OP, sortBy } from "../../../../utils/sieve-builder.js";
|
|
30
|
+
import { PRODUCT_FIELDS } from "../../../../constants/field-names.js";
|
|
31
|
+
import { isListingRowAvailable, unavailableClausesFor, } from "../../../shared/listing-types/_registry.js";
|
|
32
|
+
import { normalizeListingType } from "../../../../features/products/utils/listing-type.js";
|
|
33
|
+
import { clauseToSieve, PUBLIC_PRODUCT_MAX_PAGE_SIZE } from "./list-public.js";
|
|
28
34
|
const DEFAULT_STATUS = "published";
|
|
29
35
|
/** Bundles a category owns, resolved by the same rule the bundles tab renders. */
|
|
30
36
|
async function countBundles(scope) {
|
|
@@ -65,6 +71,101 @@ function bundleMatchesScope(bundle, scope) {
|
|
|
65
71
|
}
|
|
66
72
|
return true;
|
|
67
73
|
}
|
|
74
|
+
/**
|
|
75
|
+
* How many rows in scope are NOT available, per listing type.
|
|
76
|
+
*
|
|
77
|
+
* โโ ๐ WHY THIS IS NOT AN AGGREGATE COUNT โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
|
|
78
|
+
*
|
|
79
|
+
* Two independent reasons, either of which alone is disqualifying.
|
|
80
|
+
*
|
|
81
|
+
* 1. Unavailability is an OR across DIFFERENT fields (`availableQuantity == 0`
|
|
82
|
+
* OR `isSold == true` OR `prizeRevealStatus == closed` โฆ). Firestore cannot
|
|
83
|
+
* express that in one query, and counting each clause separately and adding
|
|
84
|
+
* them double-counts every row matching two of them.
|
|
85
|
+
*
|
|
86
|
+
* 2. A clause selects a SUPERSET, deliberately. `madeToOrder` is pointedly NOT
|
|
87
|
+
* a clause (see the note in `availability.ts`), so a print-on-demand sticker
|
|
88
|
+
* sitting at `availableQuantity: 0` matches `availableQuantity == 0` and is
|
|
89
|
+
* nevertheless AVAILABLE. Only `isListingRowAvailable` knows that, and it is
|
|
90
|
+
* a per-row predicate. Measured on the category that prompted this work,
|
|
91
|
+
* a clause-count would have badged "Art & Stickers 2" over three available
|
|
92
|
+
* rows โ trading one wrong number for another.
|
|
93
|
+
*
|
|
94
|
+
* So this is Path C of `listPublicProducts` reused in miniature: one bounded
|
|
95
|
+
* query per DISTINCT clause, in parallel, merged and deduped by id, then every
|
|
96
|
+
* surviving row confirmed with the real predicate. Nine listing types collapse
|
|
97
|
+
* to five clauses, so the whole tab bar costs ~5 ร 50 documents in one parallel
|
|
98
|
+
* round โ the same fan-out and the same budget that scope already spends.
|
|
99
|
+
*
|
|
100
|
+
* ๐ The queries are NOT narrowed to the tab's listing types. Firestore permits
|
|
101
|
+
* one array operator per query and the category scope already spends it on
|
|
102
|
+
* `categorySlugs array-contains-any`, so adding `listingType in (โฆ)` risks an
|
|
103
|
+
* INVALID_ARGUMENT for no gain: unavailable rows are sparse, and tallying each
|
|
104
|
+
* row by its own `listingType` afterwards answers every tab from one pass.
|
|
105
|
+
*/
|
|
106
|
+
async function unavailableByType(scope, types, now) {
|
|
107
|
+
const byType = new Map();
|
|
108
|
+
const clauses = unavailableClausesFor(types);
|
|
109
|
+
if (clauses.length === 0)
|
|
110
|
+
return { byType, truncated: false };
|
|
111
|
+
const status = scope.status ?? DEFAULT_STATUS;
|
|
112
|
+
const results = await Promise.all(clauses.map(async (clause) => {
|
|
113
|
+
// An inequality must be ordered by its own field โ Firestore appends it
|
|
114
|
+
// to the `orderBy` implicitly, so any other sort demands a composite
|
|
115
|
+
// index nobody declared (Root Cause #59).
|
|
116
|
+
const sorts = clause.op === "lt" && clause.sortField
|
|
117
|
+
? sortBy(clause.sortField, "DESC")
|
|
118
|
+
: sortBy(PRODUCT_FIELDS.CREATED_AT, "DESC");
|
|
119
|
+
try {
|
|
120
|
+
return await productRepository.list({
|
|
121
|
+
filters: sieveAnd(sieveFilter(PRODUCT_FIELDS.STATUS, SIEVE_OP.EQ, status), clauseToSieve(clause, now)),
|
|
122
|
+
sorts,
|
|
123
|
+
page: 1,
|
|
124
|
+
pageSize: PUBLIC_PRODUCT_MAX_PAGE_SIZE,
|
|
125
|
+
}, {
|
|
126
|
+
storeId: scope.storeId,
|
|
127
|
+
categoriesIn: scope.categoriesIn,
|
|
128
|
+
brandName: scope.brandName,
|
|
129
|
+
});
|
|
130
|
+
}
|
|
131
|
+
catch (error) {
|
|
132
|
+
const e = normalizeError(error);
|
|
133
|
+
// ERROR, not warn: a clause that cannot run is almost always a missing
|
|
134
|
+
// composite index โ a permanent, silent skew of every badge on the
|
|
135
|
+
// page rather than a transient blip.
|
|
136
|
+
serverLogger.error(`listingTabCounts: unavailable clause failed (${clause.field} ${clause.op}); badges fall back to totals`, { field: clause.field, op: clause.op, error: e.message });
|
|
137
|
+
return null;
|
|
138
|
+
}
|
|
139
|
+
}));
|
|
140
|
+
const merged = new Map();
|
|
141
|
+
let truncated = false;
|
|
142
|
+
for (const result of results) {
|
|
143
|
+
if (!result) {
|
|
144
|
+
truncated = true;
|
|
145
|
+
continue;
|
|
146
|
+
}
|
|
147
|
+
const rows = result.items;
|
|
148
|
+
if (rows.length >= PUBLIC_PRODUCT_MAX_PAGE_SIZE)
|
|
149
|
+
truncated = true;
|
|
150
|
+
for (const row of rows) {
|
|
151
|
+
const id = typeof row.id === "string" ? row.id : JSON.stringify(row.id);
|
|
152
|
+
if (!merged.has(id))
|
|
153
|
+
merged.set(id, row);
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
for (const row of merged.values()) {
|
|
157
|
+
// The clause matched a superset; this is the real answer.
|
|
158
|
+
if (isListingRowAvailable(row, now))
|
|
159
|
+
continue;
|
|
160
|
+
const type = normalizeListingType(row);
|
|
161
|
+
byType.set(type, (byType.get(type) ?? 0) + 1);
|
|
162
|
+
}
|
|
163
|
+
return { byType, truncated };
|
|
164
|
+
}
|
|
165
|
+
/** The listing types one tab spans โ `"art|stickers"` is a pipe OR-group. */
|
|
166
|
+
function typesOf(tab) {
|
|
167
|
+
return String(tab.listingType).split("|").filter(Boolean);
|
|
168
|
+
}
|
|
68
169
|
/**
|
|
69
170
|
* Count every tab in `tabs` within `scope`, in one parallel batch.
|
|
70
171
|
*
|
|
@@ -75,10 +176,12 @@ function bundleMatchesScope(bundle, scope) {
|
|
|
75
176
|
* `stores` is not counted here: only the category page has that tab and it
|
|
76
177
|
* already resolves its store list eagerly to render the tab body.
|
|
77
178
|
*/
|
|
78
|
-
export async function listingTabCounts(tabs, scope) {
|
|
179
|
+
export async function listingTabCounts(tabs, scope, opts) {
|
|
180
|
+
const now = opts?.now ?? new Date();
|
|
79
181
|
const listingTabs = tabs.filter((tab) => Boolean(tab.listingType));
|
|
80
182
|
const wantsBundles = tabs.some((tab) => tab.collection === "bundles");
|
|
81
|
-
const
|
|
183
|
+
const allTypes = [...new Set(listingTabs.flatMap(typesOf))];
|
|
184
|
+
const [typeCounts, bundleCount, unavailable] = await Promise.all([
|
|
82
185
|
productRepository.countByListingTypes(listingTabs.map((tab) => String(tab.listingType)), {
|
|
83
186
|
status: scope.status ?? DEFAULT_STATUS,
|
|
84
187
|
storeId: scope.storeId,
|
|
@@ -86,12 +189,27 @@ export async function listingTabCounts(tabs, scope) {
|
|
|
86
189
|
brandName: scope.brandName,
|
|
87
190
|
}),
|
|
88
191
|
wantsBundles ? countBundles(scope) : Promise.resolve(undefined),
|
|
192
|
+
unavailableByType(scope, allTypes, now),
|
|
89
193
|
]);
|
|
90
|
-
const
|
|
194
|
+
const total = {};
|
|
195
|
+
const available = {};
|
|
91
196
|
for (const tab of listingTabs) {
|
|
92
|
-
|
|
197
|
+
const totalForTab = typeCounts[String(tab.listingType)];
|
|
198
|
+
total[tab.id] = totalForTab;
|
|
199
|
+
if (totalForTab === undefined || unavailable.truncated) {
|
|
200
|
+
// Unknown beats a number that is quietly too high. Falling back to the
|
|
201
|
+
// total reproduces the pre-2026-10-20 badge rather than inventing one.
|
|
202
|
+
available[tab.id] = totalForTab;
|
|
203
|
+
continue;
|
|
204
|
+
}
|
|
205
|
+
const gone = typesOf(tab).reduce((sum, type) => sum + (unavailable.byType.get(type) ?? 0), 0);
|
|
206
|
+
available[tab.id] = Math.max(0, totalForTab - gone);
|
|
207
|
+
}
|
|
208
|
+
if (wantsBundles) {
|
|
209
|
+
// A bundle has no availability scope of its own โ the bundles tab renders
|
|
210
|
+
// active bundle ROWS, not products, so the two numbers are one number.
|
|
211
|
+
total.bundles = bundleCount;
|
|
212
|
+
available.bundles = bundleCount;
|
|
93
213
|
}
|
|
94
|
-
|
|
95
|
-
counts.bundles = bundleCount;
|
|
96
|
-
return counts;
|
|
214
|
+
return { available, total };
|
|
97
215
|
}
|
|
@@ -11,7 +11,9 @@ interface ProductDocLike {
|
|
|
11
11
|
title?: string | null;
|
|
12
12
|
price?: number | null;
|
|
13
13
|
mainImage?: string | null;
|
|
14
|
-
images?: (
|
|
14
|
+
images?: ({
|
|
15
|
+
url: string;
|
|
16
|
+
} | string | null | undefined)[] | null;
|
|
15
17
|
}
|
|
16
18
|
/** High-level OG renderer โ accepts the raw product document from `getProductForDetail`. */
|
|
17
19
|
export declare function renderProductOg(doc: ProductDocLike | null | undefined, opts: OgOptions & {
|
|
@@ -6,7 +6,9 @@ export function renderProductOg(doc, opts) {
|
|
|
6
6
|
return renderProductOgImage({
|
|
7
7
|
title: doc?.title ?? "Product",
|
|
8
8
|
price: doc?.price ?? null,
|
|
9
|
-
imageUrl: resolveOgImageUrl(doc?.mainImage ||
|
|
9
|
+
imageUrl: resolveOgImageUrl(doc?.mainImage ||
|
|
10
|
+
(typeof doc?.images?.[0] === "string" ? doc.images[0] : doc?.images?.[0]?.url) ||
|
|
11
|
+
null, opts.baseUrl),
|
|
10
12
|
}, opts.siteName);
|
|
11
13
|
}
|
|
12
14
|
export function renderProductOgImage(data, siteName) {
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import { type SearchSource } from "./search-everything";
|
|
2
|
+
/** The viewer this search is scoped TO. Null means signed out. */
|
|
3
|
+
export interface SearchViewer {
|
|
4
|
+
readonly uid: string;
|
|
5
|
+
readonly role: string;
|
|
6
|
+
/** The store slug this viewer owns, if any. See ยง Store Identity. */
|
|
7
|
+
readonly storeId: string | null;
|
|
8
|
+
readonly isAdmin: boolean;
|
|
9
|
+
readonly isSeller: boolean;
|
|
10
|
+
}
|
|
11
|
+
/**
|
|
12
|
+
* Build the viewer's sources.
|
|
13
|
+
*
|
|
14
|
+
* ๐ Returns `[]` for a guest, which is what makes "zero authenticated
|
|
15
|
+
* requests when signed out" structural rather than a caller's discipline.
|
|
16
|
+
*/
|
|
17
|
+
export declare function scopedSourcesFor(viewer: SearchViewer | null): SearchSource[];
|
|
18
|
+
/**
|
|
19
|
+
* ฮฃ `scanLimit`, for the budget check.
|
|
20
|
+
*
|
|
21
|
+
* Exported so `audit-scoped-search-ownership` R4 can print the arithmetic
|
|
22
|
+
* rather than just a verdict: "over budget" without the per-source figures
|
|
23
|
+
* tells you nothing about which source to shrink.
|
|
24
|
+
*/
|
|
25
|
+
export declare function scopedScanTotal(sources: readonly SearchSource[]): number;
|
|
26
|
+
export declare function assertWithinScopedBudget(sources: readonly SearchSource[]): void;
|
|
@@ -0,0 +1,252 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* WHY: A signed-in viewer should be able to find their own orders, offers
|
|
3
|
+
* and drafts from the same box โ without that box ever being able to
|
|
4
|
+
* return somebody else's.
|
|
5
|
+
* WHAT: Tier 2. Viewer-scoped `SearchSource`s, built per request from the
|
|
6
|
+
* session, plus the budget check.
|
|
7
|
+
*
|
|
8
|
+
* ## ๐ Filtered SERVER-side, never in the browser
|
|
9
|
+
*
|
|
10
|
+
* The same rule `projectActionIndexForViewer` states for the action index.
|
|
11
|
+
* Every source below carries an OWNER CLAUSE in its `where`, so the query
|
|
12
|
+
* itself cannot return another viewer's rows โ a client-side filter over a
|
|
13
|
+
* wider result is one rendering bug away from a disclosure, and the bug is
|
|
14
|
+
* invisible in review because the UI looks right.
|
|
15
|
+
*
|
|
16
|
+
* ## ๐ A guest makes ZERO authenticated requests
|
|
17
|
+
*
|
|
18
|
+
* `scopedSourcesFor(null)` returns `[]`, and the route refuses before
|
|
19
|
+
* touching Firestore. This is not an optimisation: a signed-out visitor
|
|
20
|
+
* whose search fires an authenticated request has a session the page does
|
|
21
|
+
* not know about, and the only honest way to prove the gate works is for
|
|
22
|
+
* there to be no request at all.
|
|
23
|
+
*
|
|
24
|
+
* ## The read budget, which is why the split exists
|
|
25
|
+
*
|
|
26
|
+
* guest 0 sources 0 docs 0 round trips
|
|
27
|
+
* buyer 6 ~120 1 (one Promise.all)
|
|
28
|
+
* seller +5 ~220 1
|
|
29
|
+
* admin +5 ~320 1
|
|
30
|
+
*
|
|
31
|
+
* Against `SCOPED_SCAN_BUDGET` = 400. Scoped sources use `scanLimit: 20`
|
|
32
|
+
* rather than the 60 the public tier uses, because an owner clause has
|
|
33
|
+
* already collapsed the candidate set โ a 60-doc window would be 40 reads
|
|
34
|
+
* spent proving there was nothing else.
|
|
35
|
+
*
|
|
36
|
+
* @tag domain:search
|
|
37
|
+
* @tag layer:server
|
|
38
|
+
* @tag pattern:none
|
|
39
|
+
* @tag access:server
|
|
40
|
+
* @tag consumers:/api/search/scoped
|
|
41
|
+
* @tag sideEffects:none
|
|
42
|
+
*/
|
|
43
|
+
import { ROUTES } from "../../../../next/routing/route-map.js";
|
|
44
|
+
import { SCOPED_SCAN_BUDGET } from "./search-everything.js";
|
|
45
|
+
/** Scoped sources read a narrow window; the owner clause did the work. */
|
|
46
|
+
const SCOPED_SCAN = 20;
|
|
47
|
+
const str = (v) => typeof v === "string" && v.length > 0 ? v : undefined;
|
|
48
|
+
/**
|
|
49
|
+
* Every term must appear somewhere in the named fields.
|
|
50
|
+
*
|
|
51
|
+
* ๐ AND, matching the push-down path's `plan.rest.every(...)`. An OR here
|
|
52
|
+
* would make the two modes disagree about what a two-word query means, on
|
|
53
|
+
* the same page, in adjacent groups.
|
|
54
|
+
*
|
|
55
|
+
* ๐ Lower-cased comparison only โ NOT `normalizeSearchText`. That
|
|
56
|
+
* normaliser exists to make the WRITE path and the token query agree; this
|
|
57
|
+
* path has no tokens, and running it here would strip punctuation out of a
|
|
58
|
+
* tracking number or an order id, which are exactly the strings someone
|
|
59
|
+
* pastes into a scoped search.
|
|
60
|
+
*/
|
|
61
|
+
function fieldsContain(fields) {
|
|
62
|
+
return (data, terms) => {
|
|
63
|
+
if (terms.length === 0)
|
|
64
|
+
return false;
|
|
65
|
+
const hay = fields
|
|
66
|
+
.map((f) => str(data[f]) ?? "")
|
|
67
|
+
.join(" ")
|
|
68
|
+
.toLowerCase();
|
|
69
|
+
return terms.every((t) => hay.includes(t.toLowerCase()));
|
|
70
|
+
};
|
|
71
|
+
}
|
|
72
|
+
/**
|
|
73
|
+
* The viewer's own records.
|
|
74
|
+
*
|
|
75
|
+
* ๐ `mode: "bounded-memory"` for everything PII-bearing, and `search-txt`
|
|
76
|
+
* only where the collection genuinely carries tokens that are safe to have.
|
|
77
|
+
* `orders` is the interesting case: `buildOrderSearchTxt` already indexes
|
|
78
|
+
* productTitle / storeName / trackingNumber and already EXCLUDES the
|
|
79
|
+
* shipping address, so it keeps the push-down. `addresses` has no
|
|
80
|
+
* `searchTxt` and must never grow one.
|
|
81
|
+
*/
|
|
82
|
+
function selfSources(viewer) {
|
|
83
|
+
return [
|
|
84
|
+
{
|
|
85
|
+
key: "my-orders",
|
|
86
|
+
label: "Your orders",
|
|
87
|
+
collection: "orders",
|
|
88
|
+
// ๐ `userId`, NOT `buyerId`. The field is `userId` and a query on
|
|
89
|
+
// `buyerId` returns ZERO while raising nothing โ indistinguishable
|
|
90
|
+
// from "this buyer has no orders", which is a false-finding generator.
|
|
91
|
+
where: [["userId", viewer.uid]],
|
|
92
|
+
scanLimit: SCOPED_SCAN,
|
|
93
|
+
mode: "search-txt",
|
|
94
|
+
select: ["productTitle", "status", "trackingNumber"],
|
|
95
|
+
toHit: (id, d) => ({
|
|
96
|
+
id,
|
|
97
|
+
title: str(d.productTitle) ?? `Order ${id}`,
|
|
98
|
+
href: String(ROUTES.USER.ORDER_DETAIL(id)),
|
|
99
|
+
detail: str(d.status),
|
|
100
|
+
}),
|
|
101
|
+
moreHref: () => String(ROUTES.USER.ORDERS),
|
|
102
|
+
},
|
|
103
|
+
{
|
|
104
|
+
key: "my-offers",
|
|
105
|
+
label: "Your offers",
|
|
106
|
+
collection: "offers",
|
|
107
|
+
where: [["buyerId", viewer.uid]],
|
|
108
|
+
scanLimit: SCOPED_SCAN,
|
|
109
|
+
mode: "bounded-memory",
|
|
110
|
+
orderBy: ["createdAt", "desc"],
|
|
111
|
+
select: ["productTitle", "status", "productSlug"],
|
|
112
|
+
matches: fieldsContain(["productTitle", "status"]),
|
|
113
|
+
toHit: (id, d) => ({
|
|
114
|
+
id,
|
|
115
|
+
title: str(d.productTitle) ?? "Offer",
|
|
116
|
+
href: String(ROUTES.USER.OFFERS),
|
|
117
|
+
detail: str(d.status),
|
|
118
|
+
}),
|
|
119
|
+
moreHref: () => String(ROUTES.USER.OFFERS),
|
|
120
|
+
},
|
|
121
|
+
{
|
|
122
|
+
key: "my-catalogue",
|
|
123
|
+
label: "Your catalogue",
|
|
124
|
+
collection: "catalogueItems",
|
|
125
|
+
where: [["ownerId", viewer.uid]],
|
|
126
|
+
scanLimit: SCOPED_SCAN,
|
|
127
|
+
mode: "bounded-memory",
|
|
128
|
+
orderBy: ["createdAt", "desc"],
|
|
129
|
+
select: ["title", "listingStatus"],
|
|
130
|
+
matches: fieldsContain(["title"]),
|
|
131
|
+
toHit: (id, d) => ({
|
|
132
|
+
id,
|
|
133
|
+
title: str(d.title) ?? "Catalogue item",
|
|
134
|
+
href: String(ROUTES.USER.CATALOGUE),
|
|
135
|
+
detail: str(d.listingStatus),
|
|
136
|
+
}),
|
|
137
|
+
moreHref: () => String(ROUTES.USER.CATALOGUE),
|
|
138
|
+
},
|
|
139
|
+
{
|
|
140
|
+
/*
|
|
141
|
+
* ๐ `bounded-memory`, and this is the source the mode exists for.
|
|
142
|
+
*
|
|
143
|
+
* `addresses` is PII โ `fullName`, `phone`, `addressLine1` are all
|
|
144
|
+
* encrypted at rest and decrypted by the repository on read. It has
|
|
145
|
+
* no `searchTxt` and must never get one: a prefix-expanded plaintext
|
|
146
|
+
* token array over a person's name and street is precisely what
|
|
147
|
+
* ยง Searching PII refuses. The owner clause already bounds this to
|
|
148
|
+
* well under 25 rows, so an index would re-derive a bound we have
|
|
149
|
+
* while adding leak surface for nothing.
|
|
150
|
+
*/
|
|
151
|
+
key: "my-addresses",
|
|
152
|
+
label: "Your addresses",
|
|
153
|
+
collection: "addresses",
|
|
154
|
+
where: [
|
|
155
|
+
["ownerType", "user"],
|
|
156
|
+
["ownerId", viewer.uid],
|
|
157
|
+
],
|
|
158
|
+
scanLimit: SCOPED_SCAN,
|
|
159
|
+
mode: "bounded-memory",
|
|
160
|
+
select: ["label", "city", "state"],
|
|
161
|
+
// Only the non-sensitive half is matched AND projected. A label and a
|
|
162
|
+
// city are enough to pick an address out of a list of four; the name,
|
|
163
|
+
// phone and street line never leave the server.
|
|
164
|
+
matches: fieldsContain(["label", "city", "state"]),
|
|
165
|
+
toHit: (id, d) => ({
|
|
166
|
+
id,
|
|
167
|
+
title: str(d.label) ?? "Address",
|
|
168
|
+
href: String(ROUTES.USER.ADDRESSES),
|
|
169
|
+
detail: [str(d.city), str(d.state)].filter(Boolean).join(", ") || undefined,
|
|
170
|
+
}),
|
|
171
|
+
moreHref: () => String(ROUTES.USER.ADDRESSES),
|
|
172
|
+
},
|
|
173
|
+
];
|
|
174
|
+
}
|
|
175
|
+
/** The viewer's store. Requires a resolved `storeId`, never a uid. */
|
|
176
|
+
function sellerSources(viewer) {
|
|
177
|
+
if (!viewer.storeId)
|
|
178
|
+
return [];
|
|
179
|
+
const storeId = viewer.storeId;
|
|
180
|
+
return [
|
|
181
|
+
{
|
|
182
|
+
/*
|
|
183
|
+
* The seller's own listings INCLUDING drafts โ which is the whole
|
|
184
|
+
* point, because a published one is already in the public artifact and
|
|
185
|
+
* the merge dedupes it away. What survives in this group is precisely
|
|
186
|
+
* "your drafts", and labelling it so is what makes the dedupe legible.
|
|
187
|
+
*/
|
|
188
|
+
key: "my-listings",
|
|
189
|
+
label: "Your listings (incl. drafts)",
|
|
190
|
+
collection: "products",
|
|
191
|
+
where: [["storeId", storeId]],
|
|
192
|
+
scanLimit: SCOPED_SCAN,
|
|
193
|
+
mode: "search-txt",
|
|
194
|
+
select: ["title", "slug", "status"],
|
|
195
|
+
toHit: (id, d) => ({
|
|
196
|
+
id,
|
|
197
|
+
title: str(d.title) ?? "Listing",
|
|
198
|
+
href: String(ROUTES.STORE.PRODUCTS_EDIT(str(d.slug) ?? id)),
|
|
199
|
+
detail: str(d.status),
|
|
200
|
+
}),
|
|
201
|
+
moreHref: () => String(ROUTES.STORE.PRODUCTS),
|
|
202
|
+
},
|
|
203
|
+
{
|
|
204
|
+
key: "store-orders",
|
|
205
|
+
label: "Orders in your store",
|
|
206
|
+
collection: "orders",
|
|
207
|
+
where: [["storeId", storeId]],
|
|
208
|
+
scanLimit: SCOPED_SCAN,
|
|
209
|
+
mode: "search-txt",
|
|
210
|
+
select: ["productTitle", "status", "trackingNumber"],
|
|
211
|
+
toHit: (id, d) => ({
|
|
212
|
+
id,
|
|
213
|
+
title: str(d.productTitle) ?? `Order ${id}`,
|
|
214
|
+
href: String(ROUTES.STORE.ORDER_DETAIL(id)),
|
|
215
|
+
detail: str(d.status),
|
|
216
|
+
}),
|
|
217
|
+
moreHref: () => String(ROUTES.STORE.ORDERS),
|
|
218
|
+
},
|
|
219
|
+
];
|
|
220
|
+
}
|
|
221
|
+
/**
|
|
222
|
+
* Build the viewer's sources.
|
|
223
|
+
*
|
|
224
|
+
* ๐ Returns `[]` for a guest, which is what makes "zero authenticated
|
|
225
|
+
* requests when signed out" structural rather than a caller's discipline.
|
|
226
|
+
*/
|
|
227
|
+
export function scopedSourcesFor(viewer) {
|
|
228
|
+
if (!viewer)
|
|
229
|
+
return [];
|
|
230
|
+
const sources = [...selfSources(viewer)];
|
|
231
|
+
if (viewer.isSeller || viewer.isAdmin)
|
|
232
|
+
sources.push(...sellerSources(viewer));
|
|
233
|
+
return sources;
|
|
234
|
+
}
|
|
235
|
+
/**
|
|
236
|
+
* ฮฃ `scanLimit`, for the budget check.
|
|
237
|
+
*
|
|
238
|
+
* Exported so `audit-scoped-search-ownership` R4 can print the arithmetic
|
|
239
|
+
* rather than just a verdict: "over budget" without the per-source figures
|
|
240
|
+
* tells you nothing about which source to shrink.
|
|
241
|
+
*/
|
|
242
|
+
export function scopedScanTotal(sources) {
|
|
243
|
+
return sources.reduce((n, s) => n + (s.scanLimit ?? 60), 0);
|
|
244
|
+
}
|
|
245
|
+
export function assertWithinScopedBudget(sources) {
|
|
246
|
+
const total = scopedScanTotal(sources);
|
|
247
|
+
if (total <= SCOPED_SCAN_BUDGET)
|
|
248
|
+
return;
|
|
249
|
+
const breakdown = sources.map((s) => `${s.key}=${s.scanLimit ?? 60}`).join(" + ");
|
|
250
|
+
throw new Error(`Scoped search would read up to ${total} documents, over SCOPED_SCAN_BUDGET ` +
|
|
251
|
+
`(${SCOPED_SCAN_BUDGET}). ${breakdown} = ${total}.`);
|
|
252
|
+
}
|
|
@@ -43,7 +43,56 @@ export interface SearchSource {
|
|
|
43
43
|
moreHref: (q: string) => string;
|
|
44
44
|
/** Extra equality narrowing, e.g. `status == published`. */
|
|
45
45
|
where?: ReadonlyArray<readonly [string, unknown]>;
|
|
46
|
+
/**
|
|
47
|
+
* The bounded window this source reads before the in-memory refine.
|
|
48
|
+
*
|
|
49
|
+
* Defaults to `SCAN` (60). Scoped sources pass 20: an owner clause
|
|
50
|
+
* (`userId == uid`) has already collapsed the candidate set, so a 60-doc
|
|
51
|
+
* window is 40 reads spent proving there was nothing else.
|
|
52
|
+
*/
|
|
53
|
+
scanLimit?: number;
|
|
54
|
+
/**
|
|
55
|
+
* How this source FINDS rows. Discriminated so TypeScript forces every
|
|
56
|
+
* source to answer, which is what makes `audit-scoped-search-ownership`
|
|
57
|
+
* R2 checkable rather than advisory.
|
|
58
|
+
*
|
|
59
|
+
* `search-txt` the `array-contains` push-down. Requires the
|
|
60
|
+
* collection to carry `searchTxt`.
|
|
61
|
+
* `bounded-memory` read the owner's bounded window and filter in
|
|
62
|
+
* memory, with no token push-down at all.
|
|
63
|
+
*
|
|
64
|
+
* ๐ A PII-bearing collection must be `bounded-memory`. `searchTxt` is
|
|
65
|
+
* plaintext, edge-n-gram-expanded and stored on the document โ giving
|
|
66
|
+
* `addresses` a token push-down would write a prefix-expanded index over
|
|
67
|
+
* a person's name and street, which is the precise thing ยง Searching PII
|
|
68
|
+
* refuses (a 1-character prefix has 36 possible values; the frequency
|
|
69
|
+
* distribution unpicks from there). Decrypting a bounded owner-scoped
|
|
70
|
+
* window in memory re-derives a bound ownership already gives, and adds
|
|
71
|
+
* no leak surface.
|
|
72
|
+
*/
|
|
73
|
+
mode?: "search-txt" | "bounded-memory";
|
|
74
|
+
/**
|
|
75
|
+
* For `bounded-memory`: does this row match? Receives the DECRYPTED
|
|
76
|
+
* document, so a repository that decrypts on read is the intended caller.
|
|
77
|
+
* Every term must match (AND), consistent with the push-down path.
|
|
78
|
+
*/
|
|
79
|
+
matches?: (data: SearchDocData, terms: readonly string[]) => boolean;
|
|
80
|
+
/** Ordering for `bounded-memory`, since there is no token clause to sort by. */
|
|
81
|
+
orderBy?: readonly [string, "asc" | "desc"];
|
|
46
82
|
}
|
|
83
|
+
/**
|
|
84
|
+
* ๐ The ceiling on what ONE viewer's scoped search may read.
|
|
85
|
+
*
|
|
86
|
+
* ฮฃ `scanLimit` across the widest role, checked by
|
|
87
|
+
* `audit-scoped-search-ownership` R4 โ which PRINTS THE ARITHMETIC on
|
|
88
|
+
* failure, because "the budget is exceeded" is useless without knowing by
|
|
89
|
+
* which source and how much.
|
|
90
|
+
*
|
|
91
|
+
* 400 against Rule #6's ~3-round-trip guidance is defensible because the
|
|
92
|
+
* sources run in ONE `Promise.all`: it is one round trip of fan-out, not
|
|
93
|
+
* twelve sequential ones. The number that matters is documents read.
|
|
94
|
+
*/
|
|
95
|
+
export declare const SCOPED_SCAN_BUDGET = 400;
|
|
47
96
|
/** A projected Firestore document's fields. */
|
|
48
97
|
export type SearchDocData = Record<string, FirestoreValue>;
|
|
49
98
|
/** A projected document โ the only shape this module reads. */
|
|
@@ -55,6 +104,14 @@ interface SearchQueryRef {
|
|
|
55
104
|
where(field: string, op: string, value: unknown): SearchQueryRef;
|
|
56
105
|
select(...fields: string[]): SearchQueryRef;
|
|
57
106
|
limit(n: number): SearchQueryRef;
|
|
107
|
+
/**
|
|
108
|
+
* Only `bounded-memory` sources use this. The push-down path deliberately
|
|
109
|
+
* does not order: an `orderBy` beside an `array-contains` demands a
|
|
110
|
+
* composite index per source, and a missing one throws
|
|
111
|
+
* `FAILED_PRECONDITION` into the per-source catch, where it renders as an
|
|
112
|
+
* empty group โ i.e. silently (Root Cause #59).
|
|
113
|
+
*/
|
|
114
|
+
orderBy(field: string, direction: "asc" | "desc"): SearchQueryRef;
|
|
58
115
|
get(): Promise<{
|
|
59
116
|
docs: SearchDoc[];
|
|
60
117
|
}>;
|