@mohasinac/appkit 4.52.5 → 4.53.0
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 +4 -4
- package/dist/_internal/client/features/sticker-trace/canvas-codec.d.ts +2 -0
- package/dist/_internal/client/features/sticker-trace/canvas-codec.js +148 -0
- package/dist/_internal/server/features/auctions/actions.js +1 -1
- package/dist/_internal/server/features/categories/data.js +23 -7
- package/dist/_internal/server/features/checkout/actions.js +10 -3
- package/dist/_internal/server/features/checkout/data.d.ts +15 -0
- package/dist/_internal/server/features/checkout/data.js +20 -3
- package/dist/_internal/server/features/lottery/actions.js +9 -1
- package/dist/_internal/server/features/lottery/og.d.ts +40 -0
- package/dist/_internal/server/features/lottery/og.js +79 -0
- package/dist/_internal/server/features/messages/actions.js +4 -0
- package/dist/_internal/server/features/offers/adapters.js +17 -3
- package/dist/_internal/server/features/orders/actions.js +7 -2
- package/dist/_internal/server/features/raffle/actions.d.ts +19 -0
- package/dist/_internal/server/features/raffle/actions.js +25 -0
- package/dist/_internal/server/features/reviews/actions.js +1 -1
- package/dist/_internal/server/features/reviews/data.d.ts +1 -1
- package/dist/_internal/server/features/search/index-build/artifact-store.d.ts +89 -0
- package/dist/_internal/server/features/search/index-build/artifact-store.js +208 -0
- package/dist/_internal/server/features/search/index-build/build.d.ts +57 -0
- package/dist/_internal/server/features/search/index-build/build.js +285 -0
- package/dist/_internal/server/features/search/index-build/sources.d.ts +53 -0
- package/dist/_internal/server/features/search/index-build/sources.js +385 -0
- package/dist/_internal/server/features/search/search-sources.d.ts +0 -15
- package/dist/_internal/server/features/search/search-sources.js +142 -7
- package/dist/_internal/server/features/seo/robots.js +21 -1
- package/dist/_internal/server/features/seo/sitemap.js +1 -1
- package/dist/_internal/server/features/shared/listing-data-factory.js +16 -4
- package/dist/_internal/server/features/site-settings/adapters.d.ts +1 -1
- package/dist/_internal/server/features/site-settings/adapters.js +26 -0
- package/dist/_internal/server/features/sticker-trace/actions.d.ts +109 -0
- package/dist/_internal/server/features/sticker-trace/actions.js +172 -0
- package/dist/_internal/server/features/sticker-trace/adapters.d.ts +80 -0
- package/dist/_internal/server/features/sticker-trace/adapters.js +174 -0
- package/dist/_internal/server/features/sticker-trace/build-cut-file.d.ts +26 -0
- package/dist/_internal/server/features/sticker-trace/build-cut-file.js +127 -0
- package/dist/_internal/server/features/sticker-trace/coordinator.d.ts +27 -0
- package/dist/_internal/server/features/sticker-trace/coordinator.js +169 -0
- package/dist/_internal/server/features/sticker-trace/fanout.d.ts +43 -0
- package/dist/_internal/server/features/sticker-trace/fanout.js +180 -0
- package/dist/_internal/server/features/sticker-trace/finalizer.d.ts +25 -0
- package/dist/_internal/server/features/sticker-trace/finalizer.js +123 -0
- package/dist/_internal/server/features/sticker-trace/mosaic-inputs.d.ts +30 -0
- package/dist/_internal/server/features/sticker-trace/mosaic-inputs.js +112 -0
- package/dist/_internal/server/features/sticker-trace/progress.d.ts +81 -0
- package/dist/_internal/server/features/sticker-trace/progress.js +198 -0
- package/dist/_internal/server/features/sticker-trace/promote.d.ts +23 -0
- package/dist/_internal/server/features/sticker-trace/promote.js +193 -0
- package/dist/_internal/server/features/sticker-trace/shard.d.ts +50 -0
- package/dist/_internal/server/features/sticker-trace/shard.js +174 -0
- package/dist/_internal/server/features/sticker-trace/sheet-actions.d.ts +141 -0
- package/dist/_internal/server/features/sticker-trace/sheet-actions.js +279 -0
- package/dist/_internal/server/features/sticker-trace/storage.d.ts +135 -0
- package/dist/_internal/server/features/sticker-trace/storage.js +282 -0
- package/dist/_internal/server/functions/scheduled.d.ts +2 -1
- package/dist/_internal/server/functions/scheduled.js +25 -1
- package/dist/_internal/server/jobs/core/_event-ref.d.ts +51 -0
- package/dist/_internal/server/jobs/core/_event-ref.js +63 -0
- package/dist/_internal/server/jobs/core/assignSpinPrize.d.ts +18 -0
- package/dist/_internal/server/jobs/core/assignSpinPrize.js +77 -16
- package/dist/_internal/server/jobs/core/auctionSettlement.js +13 -4
- package/dist/_internal/server/jobs/core/autoPayoutEligibility.js +44 -15
- package/dist/_internal/server/jobs/core/bundleStockSync.js +3 -4
- package/dist/_internal/server/jobs/core/cleanupRtdbEvents.js +44 -0
- package/dist/_internal/server/jobs/core/event-draw-reasons.d.ts +78 -0
- package/dist/_internal/server/jobs/core/event-draw-reasons.js +133 -0
- package/dist/_internal/server/jobs/core/listingProcessor.js +28 -8
- package/dist/_internal/server/jobs/core/onOrderCreate.js +12 -1
- package/dist/_internal/server/jobs/core/onScamReportCreate.js +13 -2
- package/dist/_internal/server/jobs/core/onScamReportRejected.js +8 -2
- package/dist/_internal/server/jobs/core/onScamReportVerified.js +8 -2
- package/dist/_internal/server/jobs/core/onStoreWrite.js +11 -4
- package/dist/_internal/server/jobs/core/prizeDrawAssignWinner.js +5 -4
- package/dist/_internal/server/jobs/core/prizeDrawExpiryReveal.js +1 -4
- package/dist/_internal/server/jobs/core/prizeDrawSoldOutReveal.js +1 -2
- package/dist/_internal/server/jobs/core/promotions.js +7 -4
- package/dist/_internal/server/jobs/core/searchIndexBuild.d.ts +7 -0
- package/dist/_internal/server/jobs/core/searchIndexBuild.js +211 -0
- package/dist/_internal/server/jobs/core/triggerEventRaffle.d.ts +35 -0
- package/dist/_internal/server/jobs/core/triggerEventRaffle.js +213 -31
- package/dist/_internal/server/jobs/core/weeklyPayoutEligibility.js +20 -14
- package/dist/_internal/server/jobs/handlers/assignSpinPrize.d.ts +8 -0
- package/dist/_internal/server/jobs/handlers/assignSpinPrize.js +15 -15
- package/dist/_internal/server/jobs/handlers/index.d.ts +1 -0
- package/dist/_internal/server/jobs/handlers/index.js +1 -0
- package/dist/_internal/server/jobs/handlers/onCategoryWrite.js +18 -0
- package/dist/_internal/server/jobs/handlers/onProductWrite.js +18 -0
- package/dist/_internal/server/jobs/handlers/onReviewWrite.js +18 -0
- package/dist/_internal/server/jobs/handlers/onScamReportUpdate.js +18 -0
- package/dist/_internal/server/jobs/handlers/onStoreWrite.js +18 -0
- package/dist/_internal/server/jobs/handlers/searchIndexBuild.d.ts +2 -0
- package/dist/_internal/server/jobs/handlers/searchIndexBuild.js +2 -0
- package/dist/_internal/server/jobs/handlers/triggerEventRaffle.d.ts +8 -0
- package/dist/_internal/server/jobs/handlers/triggerEventRaffle.js +15 -6
- package/dist/_internal/shared/actions/action-registry.js +29 -0
- package/dist/_internal/shared/cache/revalidate-targets.d.ts +0 -1
- package/dist/_internal/shared/cache/revalidate-targets.js +10 -8
- package/dist/_internal/shared/features/events/config.d.ts +14 -0
- package/dist/_internal/shared/features/events/config.js +14 -0
- package/dist/_internal/shared/features/notifications/action-url.js +8 -0
- package/dist/_internal/shared/features/site-settings/types.d.ts +11 -0
- package/dist/_internal/shared/features/sticker-trace/codec.d.ts +81 -0
- package/dist/_internal/shared/features/sticker-trace/codec.js +30 -0
- package/dist/_internal/shared/features/sticker-trace/config.d.ts +81 -0
- package/dist/_internal/shared/features/sticker-trace/config.js +177 -0
- package/dist/_internal/shared/features/sticker-trace/cut-contour.d.ts +20 -0
- package/dist/_internal/shared/features/sticker-trace/cut-contour.js +60 -0
- package/dist/_internal/shared/features/sticker-trace/export/build-export-set.d.ts +65 -0
- package/dist/_internal/shared/features/sticker-trace/export/build-export-set.js +176 -0
- package/dist/_internal/shared/features/sticker-trace/export/cut-svg.d.ts +56 -0
- package/dist/_internal/shared/features/sticker-trace/export/cut-svg.js +120 -0
- package/dist/_internal/shared/features/sticker-trace/export/machine-profiles.d.ts +92 -0
- package/dist/_internal/shared/features/sticker-trace/export/machine-profiles.js +153 -0
- package/dist/_internal/shared/features/sticker-trace/export/nesting.d.ts +51 -0
- package/dist/_internal/shared/features/sticker-trace/export/nesting.js +110 -0
- package/dist/_internal/shared/features/sticker-trace/export/page-sizes.d.ts +68 -0
- package/dist/_internal/shared/features/sticker-trace/export/page-sizes.js +223 -0
- package/dist/_internal/shared/features/sticker-trace/export/part-tiling.d.ts +51 -0
- package/dist/_internal/shared/features/sticker-trace/export/part-tiling.js +178 -0
- package/dist/_internal/shared/features/sticker-trace/export/simplify-budget.d.ts +26 -0
- package/dist/_internal/shared/features/sticker-trace/export/simplify-budget.js +134 -0
- package/dist/_internal/shared/features/sticker-trace/export/vectorise-art.d.ts +40 -0
- package/dist/_internal/shared/features/sticker-trace/export/vectorise-art.js +195 -0
- package/dist/_internal/shared/features/sticker-trace/index.d.ts +2 -0
- package/dist/_internal/shared/features/sticker-trace/index.js +59 -0
- package/dist/_internal/shared/features/sticker-trace/math/catmull-rom.d.ts +11 -0
- package/dist/_internal/shared/features/sticker-trace/math/catmull-rom.js +66 -0
- package/dist/_internal/shared/features/sticker-trace/math/edt.d.ts +27 -0
- package/dist/_internal/shared/features/sticker-trace/math/edt.js +165 -0
- package/dist/_internal/shared/features/sticker-trace/math/label.d.ts +15 -0
- package/dist/_internal/shared/features/sticker-trace/math/label.js +156 -0
- package/dist/_internal/shared/features/sticker-trace/math/marching-squares.d.ts +16 -0
- package/dist/_internal/shared/features/sticker-trace/math/marching-squares.js +242 -0
- package/dist/_internal/shared/features/sticker-trace/math/morphology.d.ts +46 -0
- package/dist/_internal/shared/features/sticker-trace/math/morphology.js +168 -0
- package/dist/_internal/shared/features/sticker-trace/math/rasterize-path.d.ts +88 -0
- package/dist/_internal/shared/features/sticker-trace/math/rasterize-path.js +286 -0
- package/dist/_internal/shared/features/sticker-trace/math/rdp.d.ts +10 -0
- package/dist/_internal/shared/features/sticker-trace/math/rdp.js +74 -0
- package/dist/_internal/shared/features/sticker-trace/resume.d.ts +50 -0
- package/dist/_internal/shared/features/sticker-trace/resume.js +160 -0
- package/dist/_internal/shared/features/sticker-trace/trace/cluster.d.ts +20 -0
- package/dist/_internal/shared/features/sticker-trace/trace/cluster.js +132 -0
- package/dist/_internal/shared/features/sticker-trace/trace/cross-profile.d.ts +28 -0
- package/dist/_internal/shared/features/sticker-trace/trace/cross-profile.js +120 -0
- package/dist/_internal/shared/features/sticker-trace/trace/flood.d.ts +21 -0
- package/dist/_internal/shared/features/sticker-trace/trace/flood.js +104 -0
- package/dist/_internal/shared/features/sticker-trace/trace/geometry.d.ts +3 -0
- package/dist/_internal/shared/features/sticker-trace/trace/geometry.js +140 -0
- package/dist/_internal/shared/features/sticker-trace/trace/line-filter.d.ts +15 -0
- package/dist/_internal/shared/features/sticker-trace/trace/line-filter.js +138 -0
- package/dist/_internal/shared/features/sticker-trace/trace/merge.d.ts +27 -0
- package/dist/_internal/shared/features/sticker-trace/trace/merge.js +187 -0
- package/dist/_internal/shared/features/sticker-trace/trace/paper.d.ts +32 -0
- package/dist/_internal/shared/features/sticker-trace/trace/paper.js +147 -0
- package/dist/_internal/shared/features/sticker-trace/trace/prefilter.d.ts +30 -0
- package/dist/_internal/shared/features/sticker-trace/trace/prefilter.js +125 -0
- package/dist/_internal/shared/features/sticker-trace/trace/quantise.d.ts +10 -0
- package/dist/_internal/shared/features/sticker-trace/trace/quantise.js +153 -0
- package/dist/_internal/shared/features/sticker-trace/trace/ridge.d.ts +88 -0
- package/dist/_internal/shared/features/sticker-trace/trace/ridge.js +344 -0
- package/dist/_internal/shared/features/sticker-trace/trace/segment.d.ts +10 -0
- package/dist/_internal/shared/features/sticker-trace/trace/segment.js +251 -0
- package/dist/_internal/shared/features/sticker-trace/types.d.ts +197 -0
- package/dist/_internal/shared/features/sticker-trace/types.js +49 -0
- package/dist/_internal/shared/listing-types/_registry.d.ts +11 -0
- package/dist/_internal/shared/listing-types/art/config.d.ts +1 -0
- package/dist/_internal/shared/listing-types/art/config.js +1 -0
- package/dist/_internal/shared/listing-types/auction/config.d.ts +1 -0
- package/dist/_internal/shared/listing-types/auction/config.js +1 -0
- package/dist/_internal/shared/listing-types/classified/config.d.ts +1 -0
- package/dist/_internal/shared/listing-types/classified/config.js +1 -0
- package/dist/_internal/shared/listing-types/digital-code/config.d.ts +1 -0
- package/dist/_internal/shared/listing-types/digital-code/config.js +1 -0
- package/dist/_internal/shared/listing-types/feature-flags.d.ts +7 -0
- package/dist/_internal/shared/listing-types/feature-flags.js +9 -0
- package/dist/_internal/shared/listing-types/live/config.d.ts +1 -0
- package/dist/_internal/shared/listing-types/live/config.js +1 -0
- package/dist/_internal/shared/listing-types/pre-order/config.d.ts +1 -0
- package/dist/_internal/shared/listing-types/pre-order/config.js +1 -0
- package/dist/_internal/shared/listing-types/prize-draw/config.d.ts +1 -0
- package/dist/_internal/shared/listing-types/prize-draw/config.js +1 -0
- package/dist/_internal/shared/listing-types/standard/config.d.ts +1 -0
- package/dist/_internal/shared/listing-types/standard/config.js +1 -0
- package/dist/_internal/shared/listing-types/stickers/config.d.ts +1 -0
- package/dist/_internal/shared/listing-types/stickers/config.js +1 -0
- package/dist/_internal/shared/media/limits.d.ts +12 -2
- package/dist/_internal/shared/media/limits.js +12 -2
- package/dist/_internal/shared/search/artifact-url.d.ts +35 -0
- package/dist/_internal/shared/search/artifact-url.js +119 -0
- package/dist/_internal/shared/search/index-record.d.ts +68 -0
- package/dist/_internal/shared/search/index-record.js +96 -0
- package/dist/_internal/shared/search/match.d.ts +65 -0
- package/dist/_internal/shared/search/match.js +263 -0
- package/dist/client.d.ts +8 -7
- package/dist/client.js +12 -5
- package/dist/configs/eslint.js +22 -2
- package/dist/constants/api-endpoints.d.ts +68 -0
- package/dist/constants/api-endpoints.js +32 -0
- package/dist/constants/field-names.d.ts +7 -1
- package/dist/constants/field-names.js +36 -1
- package/dist/contracts/repository.d.ts +8 -7
- package/dist/contracts/search.d.ts +2 -2
- package/dist/contracts/table.d.ts +3 -3
- package/dist/contracts/table.js +3 -3
- package/dist/core/contact-submissions.repository.js +2 -2
- package/dist/core/integration-keys.js +7 -1
- package/dist/core/newsletter.repository.js +2 -2
- package/dist/core/site-config.js +1 -1
- package/dist/errors/error-codes.d.ts +30 -0
- package/dist/errors/error-codes.js +30 -0
- package/dist/errors/error-display-map.js +40 -1
- package/dist/features/account/api/[userId]/addresses/route.js +1 -1
- package/dist/features/account/components/UserBidsView.js +1 -1
- package/dist/features/account/components/index.d.ts +0 -8
- package/dist/features/account/components/index.js +0 -4
- package/dist/features/addresses/repository/addresses.repository.d.ts +17 -6
- package/dist/features/addresses/repository/addresses.repository.js +65 -9
- package/dist/features/addresses/schemas/firestore.d.ts +17 -6
- package/dist/features/admin/actions/notification-actions.js +15 -6
- package/dist/features/admin/api/bids/route.js +1 -1
- package/dist/features/admin/api/coupons/route.js +1 -1
- package/dist/features/admin/api/products/route.js +1 -1
- package/dist/features/admin/api/reviews/route.js +1 -1
- package/dist/features/admin/components/AdminAdEditorView.js +19 -5
- package/dist/features/admin/components/AdminAuditLogView.js +1 -1
- package/dist/features/admin/components/AdminBlogEditorView.js +0 -1
- package/dist/features/admin/components/AdminBlogView.js +1 -1
- package/dist/features/admin/components/AdminBrandsView.js +17 -1
- package/dist/features/admin/components/AdminCategoryEditorView.js +0 -1
- package/dist/features/admin/components/AdminContactView.js +1 -1
- package/dist/features/admin/components/AdminEmployeeEditorView.js +1 -3
- package/dist/features/admin/components/AdminNewsletterView.js +1 -1
- package/dist/features/admin/components/AdminOrdersView.js +9 -2
- package/dist/features/admin/components/AdminPayoutsView.js +1 -1
- package/dist/features/admin/components/AdminPrizeDrawsView.js +11 -0
- package/dist/features/admin/components/AdminProductEditorView.d.ts +10 -1
- package/dist/features/admin/components/AdminProductEditorView.js +24 -37
- package/dist/features/admin/components/AdminProductsView.js +10 -3
- package/dist/features/admin/components/AdminScammersView.js +1 -1
- package/dist/features/admin/components/AdminSectionsView.js +21 -5
- package/dist/features/admin/components/AdminSessionsView.js +1 -1
- package/dist/features/admin/components/AdminSublistingCategoriesView.js +22 -1
- package/dist/features/admin/components/AdminTeamView.js +1 -1
- package/dist/features/admin/components/AdminTesterChecklistItemEditorView.js +0 -1
- package/dist/features/admin/components/AdminUsersView.js +1 -1
- package/dist/features/admin/components/DataListingView.d.ts +2 -1
- package/dist/features/admin/components/DataListingView.js +9 -4
- package/dist/features/admin/components/analytics/AdminPageViewsReportView.js +0 -2
- package/dist/features/admin/hooks/useAdminListing.d.ts +1 -1
- package/dist/features/admin/repository/site-settings.repository.js +8 -3
- package/dist/features/admin/schemas/firestore.d.ts +62 -1
- package/dist/features/admin/schemas/firestore.js +32 -0
- package/dist/features/admin/types/index.d.ts +1 -1
- package/dist/features/auctions/actions/bid-actions.d.ts +2 -2
- package/dist/features/auctions/actions/bid-actions.js +16 -4
- package/dist/features/auctions/api/route.js +1 -1
- package/dist/features/auctions/components/AuctionBottomActions.js +4 -0
- package/dist/features/auctions/repository/bid.repository.d.ts +46 -4
- package/dist/features/auctions/repository/bid.repository.js +114 -13
- package/dist/features/auctions/schemas/firestore.d.ts +24 -4
- package/dist/features/auctions/schemas/firestore.js +1 -0
- package/dist/features/auth/actions/profile-actions.js +30 -4
- package/dist/features/auth/components/index.d.ts +0 -1
- package/dist/features/auth/components/index.js +0 -1
- package/dist/features/auth/repository/session.repository.d.ts +17 -3
- package/dist/features/auth/repository/session.repository.js +46 -5
- package/dist/features/auth/repository/token.repository.d.ts +27 -4
- package/dist/features/auth/repository/token.repository.js +65 -11
- package/dist/features/auth/repository/user.repository.d.ts +33 -7
- package/dist/features/auth/repository/user.repository.js +100 -15
- package/dist/features/auth/schemas/firestore.d.ts +63 -12
- package/dist/features/auth/schemas/firestore.js +11 -0
- package/dist/features/before-after/api/route.js +1 -1
- package/dist/features/blog/components/BlogListView.js +16 -3
- package/dist/features/blog/components/BlogPostView.js +1 -1
- package/dist/features/blog/schemas/index.d.ts +2 -2
- package/dist/features/cart/api/route.js +1 -1
- package/dist/features/catalogue/repository/catalogue.repository.d.ts +7 -7
- package/dist/features/catalogue/repository/catalogue.repository.js +28 -10
- package/dist/features/catalogue/schemas/firestore.d.ts +11 -0
- package/dist/features/categories/api/route.js +10 -10
- package/dist/features/categories/components/CategoryDetailPageView.js +35 -5
- package/dist/features/categories/components/CategorySelectorCreate.js +0 -1
- package/dist/features/categories/components/index.d.ts +0 -2
- package/dist/features/categories/components/index.js +0 -1
- package/dist/features/categories/repository/categories.repository.d.ts +1 -0
- package/dist/features/categories/repository/categories.repository.js +31 -1
- package/dist/features/categories/schemas/index.d.ts +2 -2
- package/dist/features/categories/utils/taxonomy-visual.d.ts +21 -0
- package/dist/features/categories/utils/taxonomy-visual.js +58 -0
- package/dist/features/checkout/actions/checkout-value-otp-actions.js +8 -1
- package/dist/features/collections/api/[slug]/route.js +3 -3
- package/dist/features/collections/api/route.js +1 -1
- package/dist/features/consultation/api/route.js +1 -1
- package/dist/features/consultation/repository/consultations.repository.js +1 -1
- package/dist/features/consultation/types/index.d.ts +1 -1
- package/dist/features/contact/components/ContactForm.js +1 -3
- package/dist/features/corporate/api/route.js +1 -1
- package/dist/features/corporate/repository/corporate.repository.js +1 -1
- package/dist/features/corporate/types/index.d.ts +1 -1
- package/dist/features/cron/jobs/auction-expiry.job.js +1 -1
- package/dist/features/cron/jobs/preorder-reminder.job.js +1 -1
- package/dist/features/events/actions/event-actions.d.ts +14 -0
- package/dist/features/events/actions/event-actions.js +82 -3
- package/dist/features/events/api/[id]/route.js +2 -2
- package/dist/features/events/components/AdminEventEditorView.js +22 -3
- package/dist/features/events/components/AdminEventsView.js +32 -8
- package/dist/features/events/components/EventRafflesSection.d.ts +34 -1
- package/dist/features/events/components/EventRafflesSection.js +52 -3
- package/dist/features/events/components/SpinWheelView.d.ts +2 -2
- package/dist/features/events/components/SpinWheelView.js +41 -4
- package/dist/features/events/repository/event-entry.repository.d.ts +14 -2
- package/dist/features/events/repository/event-entry.repository.js +41 -7
- package/dist/features/events/schemas/firestore.d.ts +17 -4
- package/dist/features/events/types/index.d.ts +37 -0
- package/dist/features/faq/repository/faqs.repository.js +1 -1
- package/dist/features/faq/schemas/index.d.ts +5 -2
- package/dist/features/faq/schemas/index.js +6 -0
- package/dist/features/faq/types/index.d.ts +0 -1
- package/dist/features/grouped/components/GroupedListingEditorView.js +17 -2
- package/dist/features/homepage/api/carousel/[id]/route.js +1 -1
- package/dist/features/homepage/api/carousel/route.js +4 -4
- package/dist/features/homepage/api/route.js +2 -2
- package/dist/features/homepage/api/sections/route.js +1 -1
- package/dist/features/homepage/components/AnnouncementBar.d.ts +1 -9
- package/dist/features/homepage/components/AnnouncementBar.js +13 -19
- package/dist/features/homepage/components/MarketplaceHomepageView.js +6 -1
- package/dist/features/homepage/components/index.d.ts +0 -2
- package/dist/features/homepage/components/index.js +0 -1
- package/dist/features/homepage/repository/homepage.repository.js +1 -1
- package/dist/features/homepage/schemas/firestore.d.ts +6 -0
- package/dist/features/homepage/schemas/index.d.ts +10 -10
- package/dist/features/layout/AppLayoutShell.d.ts +11 -0
- package/dist/features/layout/AppLayoutShell.js +64 -12
- package/dist/features/layout/BottomActionsContext.d.ts +16 -0
- package/dist/features/layout/BottomActionsContext.js +5 -0
- package/dist/features/layout/BottomChrome.js +48 -9
- package/dist/features/layout/BottomNavbar.d.ts +14 -0
- package/dist/features/layout/BottomNavbar.js +43 -3
- package/dist/features/layout/MainNavbar.d.ts +11 -0
- package/dist/features/layout/NavbarLayout.d.ts +3 -0
- package/dist/features/layout/NavbarLayout.js +88 -11
- package/dist/features/layout/SideActionDock.d.ts +1 -0
- package/dist/features/layout/SideActionDock.js +219 -0
- package/dist/features/layout/TitleBarLayout.js +1 -1
- package/dist/features/layout/cta-anchor.d.ts +31 -0
- package/dist/features/layout/cta-anchor.js +31 -0
- package/dist/features/layout/hooks/useBottomActions.d.ts +7 -0
- package/dist/features/layout/hooks/useBottomActions.js +6 -1
- package/dist/features/layout/index.d.ts +9 -1
- package/dist/features/layout/index.js +9 -1
- package/dist/features/layout/useCtaReveal.d.ts +24 -0
- package/dist/features/layout/useCtaReveal.js +82 -0
- package/dist/features/layout/useHoldToOpen.d.ts +41 -0
- package/dist/features/layout/useHoldToOpen.js +84 -0
- package/dist/features/lottery/repository/lottery-entry.repository.d.ts +18 -3
- package/dist/features/lottery/repository/lottery-entry.repository.js +48 -6
- package/dist/features/lottery/schemas/firestore.d.ts +33 -5
- package/dist/features/loyalty/api/balance/route.js +1 -1
- package/dist/features/loyalty/api/history/route.js +1 -1
- package/dist/features/media/MediaImage.js +0 -1
- package/dist/features/media/MediaPickerModal.js +7 -1
- package/dist/features/media/modals/ImageEditor.js +5 -1
- package/dist/features/media/modals/VideoThumbnailSelector.js +17 -1
- package/dist/features/media/repository/media-assets.repository.d.ts +19 -0
- package/dist/features/media/upload/CameraCapture.js +16 -1
- package/dist/features/media/upload/ImageUpload.js +42 -4
- package/dist/features/media/upload/MediaUploadField.js +69 -13
- package/dist/features/media/upload/downscale-image.d.ts +56 -0
- package/dist/features/media/upload/downscale-image.js +150 -0
- package/dist/features/messages/components/ConversationThreadView.js +45 -2
- package/dist/features/messages/components/MessageSellerButton.d.ts +15 -1
- package/dist/features/messages/components/MessageSellerButton.js +10 -2
- package/dist/features/messages/schemas/firestore.d.ts +15 -0
- package/dist/features/orders/hooks/useOrders.js +4 -2
- package/dist/features/orders/repository/orders.repository.d.ts +15 -4
- package/dist/features/orders/repository/orders.repository.js +52 -20
- package/dist/features/orders/schemas/firestore.d.ts +15 -4
- package/dist/features/orders/schemas/index.d.ts +8 -8
- package/dist/features/orders/types/index.d.ts +2 -2
- package/dist/features/payments/repository/payments.repository.js +2 -2
- package/dist/features/payments/repository/payout.repository.d.ts +23 -6
- package/dist/features/payments/repository/payout.repository.js +63 -23
- package/dist/features/payments/repository/saved-payment-methods.repository.d.ts +17 -6
- package/dist/features/payments/repository/saved-payment-methods.repository.js +59 -11
- package/dist/features/payments/schemas/firestore.d.ts +14 -3
- package/dist/features/payments/schemas/saved-methods-firestore.d.ts +12 -1
- package/dist/features/pre-orders/api/[slug]/route.js +1 -1
- package/dist/features/pre-orders/api/admin/route.js +1 -1
- package/dist/features/pre-orders/api/route.js +1 -1
- package/dist/features/pre-orders/repository/pre-orders.repository.d.ts +3 -3
- package/dist/features/pre-orders/repository/pre-orders.repository.js +6 -6
- package/dist/features/products/api/[id]/route.js +1 -1
- package/dist/features/products/components/ListingBottomActions.js +4 -0
- package/dist/features/products/components/ProductDetailPageView.js +2 -1
- package/dist/features/products/components/ProductGalleryClient.js +2 -2
- package/dist/features/products/components/ProductGrid.js +12 -1
- package/dist/features/products/components/index.d.ts +0 -2
- package/dist/features/products/components/index.js +0 -1
- package/dist/features/products/config/listing-type-listing-config.d.ts +0 -3
- package/dist/features/products/config/listing-type-listing-config.js +27 -2
- package/dist/features/products/constants/listing-tabs.d.ts +9 -0
- package/dist/features/products/constants/listing-tabs.js +3 -2
- package/dist/features/products/hooks/useProducts.js +2 -2
- package/dist/features/products/repository/products.repository.js +13 -1
- package/dist/features/products/schemas/index.d.ts +2 -2
- package/dist/features/products/types/index.d.ts +1 -1
- package/dist/features/promotions/api/route.js +1 -1
- package/dist/features/reviews/actions/review-actions.d.ts +6 -6
- package/dist/features/reviews/actions/review-actions.js +22 -8
- package/dist/features/reviews/components/ReviewDetailPageView.js +13 -1
- package/dist/features/reviews/components/ReviewsIndexPageView.js +21 -4
- package/dist/features/reviews/repository/reviews.repository.d.ts +19 -7
- package/dist/features/reviews/repository/reviews.repository.js +63 -12
- package/dist/features/reviews/schemas/firestore.d.ts +13 -2
- package/dist/features/reviews/schemas/index.d.ts +2 -2
- package/dist/features/scams/actions/scam-actions.js +16 -4
- package/dist/features/search/components/SearchResultsPageView.js +2 -1
- package/dist/features/search/hooks/useSearchIndex.d.ts +34 -0
- package/dist/features/search/hooks/useSearchIndex.js +172 -0
- package/dist/features/search/repository/search.repository.js +2 -2
- package/dist/features/seller/actions/offer-actions.d.ts +5 -5
- package/dist/features/seller/actions/offer-actions.js +38 -8
- package/dist/features/seller/actions/seller-actions.js +34 -7
- package/dist/features/seller/api/coupons/route.js +1 -1
- package/dist/features/seller/api/products/route.js +1 -1
- package/dist/features/seller/api/store/route.js +1 -1
- package/dist/features/seller/components/SellerBidsView.js +1 -1
- package/dist/features/seller/components/SellerCouponsView.js +1 -1
- package/dist/features/seller/components/SellerListingTypeView.js +9 -1
- package/dist/features/seller/components/SellerOrdersView.js +1 -1
- package/dist/features/seller/components/SellerPayoutSettingsView.js +0 -1
- package/dist/features/seller/components/SellerPayoutsView.js +1 -1
- package/dist/features/seller/components/SellerProductShell.js +0 -1
- package/dist/features/seller/components/SellerProductsView.js +1 -1
- package/dist/features/seller/components/SellerShippingView.js +0 -1
- package/dist/features/seller/components/SellerStorefrontView.js +0 -1
- package/dist/features/seller/components/index.d.ts +0 -4
- package/dist/features/seller/components/index.js +0 -2
- package/dist/features/seller/repository/offer.repository.d.ts +15 -3
- package/dist/features/seller/repository/offer.repository.js +43 -7
- package/dist/features/seller/schemas/firestore.d.ts +14 -3
- package/dist/features/seller/schemas/index.d.ts +4 -4
- package/dist/features/seller/schemas/order-forms.d.ts +39 -0
- package/dist/features/seller/schemas/order-forms.js +37 -0
- package/dist/features/shell/FormShell.js +6 -2
- package/dist/features/shell/QuickFormDrawer.js +15 -1
- package/dist/features/shipments/components/AdminShipmentEditorView.js +0 -1
- package/dist/features/shipments/components/AdminShipmentsView.js +1 -1
- package/dist/features/sticker-trace/components/StickerCommitPanel.d.ts +30 -0
- package/dist/features/sticker-trace/components/StickerCommitPanel.js +93 -0
- package/dist/features/sticker-trace/components/StickerPrintPanel.d.ts +33 -0
- package/dist/features/sticker-trace/components/StickerPrintPanel.js +104 -0
- package/dist/features/sticker-trace/components/StickerRetryPanel.d.ts +20 -0
- package/dist/features/sticker-trace/components/StickerRetryPanel.js +128 -0
- package/dist/features/sticker-trace/components/StickerRunHistoryPanel.d.ts +45 -0
- package/dist/features/sticker-trace/components/StickerRunHistoryPanel.js +84 -0
- package/dist/features/sticker-trace/components/StickerTraceStudio.d.ts +27 -0
- package/dist/features/sticker-trace/components/StickerTraceStudio.js +171 -0
- package/dist/features/sticker-trace/constants/help-content.d.ts +23 -0
- package/dist/features/sticker-trace/constants/help-content.js +172 -0
- package/dist/features/sticker-trace/hooks/useLiveTrace.d.ts +27 -0
- package/dist/features/sticker-trace/hooks/useLiveTrace.js +160 -0
- package/dist/features/sticker-trace/hooks/useStickerTraceProgress.d.ts +22 -0
- package/dist/features/sticker-trace/hooks/useStickerTraceProgress.js +106 -0
- package/dist/features/sticker-trace/repository/sticker-sheets.repository.d.ts +113 -0
- package/dist/features/sticker-trace/repository/sticker-sheets.repository.js +146 -0
- package/dist/features/sticker-trace/repository/sticker-trace-runs.repository.d.ts +132 -0
- package/dist/features/sticker-trace/repository/sticker-trace-runs.repository.js +174 -0
- package/dist/features/sticker-trace/schemas/firestore.d.ts +248 -0
- package/dist/features/sticker-trace/schemas/firestore.js +79 -0
- package/dist/features/sticker-trace/server.d.ts +8 -0
- package/dist/features/sticker-trace/server.js +51 -0
- package/dist/features/stores/actions/store-query-actions.d.ts +2 -2
- package/dist/features/stores/actions/store-query-actions.js +7 -2
- package/dist/features/stores/api/[storeSlug]/auctions/route.js +3 -3
- package/dist/features/stores/api/[storeSlug]/products/route.js +3 -3
- package/dist/features/stores/api/[storeSlug]/reviews/route.js +1 -1
- package/dist/features/stores/api/[storeSlug]/route.js +1 -1
- package/dist/features/stores/components/StoreNavTabs.js +17 -2
- package/dist/features/stores/manifest.js +8 -4
- package/dist/features/support/repository/support.repository.d.ts +14 -3
- package/dist/features/support/repository/support.repository.js +93 -13
- package/dist/features/support/schemas/firestore.d.ts +16 -5
- package/dist/features/tester/seed-data/authored/admin__events-raffles-spin.js +61 -19
- package/dist/features/tester/seed-data/authored/buying__browsing-search.js +19 -3
- 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 +243 -0
- package/dist/features/tester/seed-data/authored/content-discovery__events.js +36 -10
- package/dist/features/tester/seed-data/authored/content-discovery__overflowing-pill-rows.d.ts +2 -0
- package/dist/features/tester/seed-data/authored/content-discovery__overflowing-pill-rows.js +209 -0
- package/dist/features/tester/seed-data/authored/design-ux__header-brand-mark-centring.d.ts +2 -0
- package/dist/features/tester/seed-data/authored/design-ux__header-brand-mark-centring.js +90 -0
- package/dist/features/tester/seed-data/authored/index.js +8 -0
- package/dist/features/tester/seed-data/authored/selling__media-limits.js +19 -17
- package/dist/features/tester/seed-data/authored/selling__product-upload-details.js +10 -5
- package/dist/features/tester/seed-data/authored/tools__sticker-tracer.d.ts +2 -0
- package/dist/features/tester/seed-data/authored/tools__sticker-tracer.js +283 -0
- package/dist/features/tester/seed-data/tester-checklist-seed-data.js +192 -22
- package/dist/index.d.ts +10 -23
- package/dist/index.js +27 -19
- package/dist/next/api/routeHandler.js +9 -0
- package/dist/next/routing/require-record.d.ts +13 -0
- package/dist/next/routing/require-record.js +62 -0
- package/dist/next/routing/route-map.d.ts +4 -4
- package/dist/next/routing/route-map.js +25 -3
- package/dist/providers/db-firebase/base.d.ts +4 -2
- package/dist/providers/db-firebase/base.js +21 -5
- package/dist/providers/db-firebase/base.repository.d.ts +11 -9
- package/dist/providers/db-firebase/base.repository.js +19 -0
- package/dist/providers/db-firebase/rtdb-paths.d.ts +12 -0
- package/dist/providers/db-firebase/rtdb-paths.js +12 -0
- package/dist/react/ErrorBoundary.js +6 -1
- package/dist/react/hooks/realtime-event-constants.d.ts +1 -0
- package/dist/react/hooks/realtime-event-constants.js +8 -0
- package/dist/react/hooks/useEntityDelete.js +5 -1
- package/dist/react/hooks/useMessage.js +5 -1
- package/dist/repositories/index.d.ts +2 -2
- package/dist/repositories/index.js +6 -1
- package/dist/schemas/registry.d.ts +13 -20
- package/dist/schemas/registry.js +31 -14
- package/dist/schemas/webhooks/google-oauth.d.ts +2 -2
- package/dist/security/encrypted.d.ts +117 -0
- package/dist/security/encrypted.js +100 -0
- package/dist/security/index.d.ts +5 -2
- package/dist/security/index.js +16 -2
- package/dist/security/pii-accessors.d.ts +71 -0
- package/dist/security/pii-accessors.js +150 -0
- package/dist/security/pii-encrypt.d.ts +0 -8
- package/dist/security/pii-encrypt.js +22 -15
- package/dist/security/pii-mask.d.ts +18 -0
- package/dist/security/pii-mask.js +27 -0
- package/dist/seed/_helpers/search-txt-wrappers.d.ts +6 -6
- package/dist/seed/_helpers/search-txt-wrappers.js +22 -0
- package/dist/seed/addresses-seed-data.d.ts +2 -2
- package/dist/seed/authenticity-knowledge-seed-data.d.ts +34 -0
- package/dist/seed/authenticity-knowledge-seed-data.js +1277 -0
- package/dist/seed/bids-seed-data.d.ts +2 -2
- package/dist/seed/bids-seed-data.js +23 -2
- package/dist/seed/events-seed-data.d.ts +2 -2
- package/dist/seed/events-seed-data.js +5 -0
- package/dist/seed/faq-seed-data.js +175 -5
- package/dist/seed/homepage-sections-seed-data.js +39 -23
- package/dist/seed/index.d.ts +1 -0
- package/dist/seed/index.js +1 -0
- package/dist/seed/lottery-entries-seed-data.d.ts +2 -2
- package/dist/seed/offers-seed-data.d.ts +2 -2
- package/dist/seed/offers-seed-data.js +1 -1
- package/dist/seed/orders-seed-data.d.ts +2 -2
- package/dist/seed/payouts-seed-data.d.ts +2 -2
- package/dist/seed/payouts-seed-data.js +1 -1
- package/dist/seed/reviews-seed-data.d.ts +1 -2
- package/dist/seed/seed-purpose.js +7 -0
- package/dist/seed/sessions-seed-data.d.ts +2 -2
- package/dist/seed/sessions-seed-data.js +5 -0
- package/dist/seed/site-settings-seed-data.js +1 -1
- package/dist/seed/support-tickets-seed-data.d.ts +2 -2
- package/dist/seed/support-tickets-seed-data.js +1 -1
- package/dist/seed/users-seed-data.d.ts +2 -2
- package/dist/seed/users-seed-data.js +1 -1
- package/dist/seo/json-ld.js +5 -1
- package/dist/server.d.ts +18 -3
- package/dist/server.js +59 -3
- package/dist/styles.css +237 -1
- package/dist/tailwind-utilities.css +1 -1
- package/dist/ui/components/Button.js +7 -1
- package/dist/ui/components/FilterChipGroup.d.ts +11 -0
- package/dist/ui/components/FilterChipGroup.js +1 -1
- package/dist/ui/components/ImageGallery.js +6 -1
- package/dist/ui/components/ListingLayout.js +14 -8
- package/dist/ui/components/OverflowRow.d.ts +92 -0
- package/dist/ui/components/OverflowRow.js +203 -0
- package/dist/ui/components/OverflowRow.style.css +235 -0
- package/dist/ui/components/RecordDetailModal.js +14 -1
- package/dist/ui/components/RowActionMenu.js +5 -1
- package/dist/ui/components/SideDrawer.js +5 -1
- package/dist/ui/components/SideModal.js +9 -1
- package/dist/ui/components/TabBarShell.d.ts +12 -1
- package/dist/ui/components/TabBarShell.js +3 -2
- package/dist/ui/components/Tabs.js +36 -24
- package/dist/ui/components/Toast.d.ts +3 -24
- package/dist/ui/components/Toast.js +2 -19
- package/dist/ui/components/TopBannerPortal.d.ts +15 -0
- package/dist/ui/components/TopBannerPortal.js +19 -0
- package/dist/ui/components/UnsavedChangesModal.js +5 -1
- package/dist/ui/components/index.style.css +1 -0
- package/dist/ui/components/row-collapse.d.ts +15 -0
- package/dist/ui/components/row-collapse.js +127 -0
- package/dist/ui/components/toast-context.d.ts +63 -0
- package/dist/ui/components/toast-context.js +48 -0
- package/dist/ui/components/top-banner-slot.d.ts +37 -0
- package/dist/ui/components/top-banner-slot.js +45 -0
- package/dist/ui/icons/icon-registry.d.ts +6 -0
- package/dist/ui/icons/icon-registry.js +17 -1
- package/dist/ui/index.d.ts +2 -0
- package/dist/ui/index.js +1 -0
- package/dist/utils/array.helper.d.ts +2 -2
- package/dist/utils/array.helper.js +5 -5
- package/dist/utils/id-generators.d.ts +6 -0
- package/dist/utils/id-generators.js +10 -0
- package/dist/utils/media-url.js +0 -2
- package/dist/utils/search-txt-builders.d.ts +28 -4
- package/dist/utils/search-txt-builders.js +41 -1
- package/dist/validation/schemas.d.ts +8 -8
- package/package.json +6 -2
- package/scripts/seed-cli.mjs +3 -0
- package/dist/features/account/components/UserAddressesView.d.ts +0 -13
- package/dist/features/account/components/UserAddressesView.js +0 -5
- package/dist/features/account/components/UserNotificationsView.d.ts +0 -15
- package/dist/features/account/components/UserNotificationsView.js +0 -12
- package/dist/features/account/components/UserSettingsView.d.ts +0 -16
- package/dist/features/account/components/UserSettingsView.js +0 -13
- package/dist/features/account/components/UserSupportView.d.ts +0 -3
- package/dist/features/account/components/UserSupportView.js +0 -166
- package/dist/features/admin/components/DemoSeedView.d.ts +0 -11
- package/dist/features/admin/components/DemoSeedView.js +0 -5
- package/dist/features/auth/components/AuthStatusPanel.d.ts +0 -10
- package/dist/features/auth/components/AuthStatusPanel.js +0 -22
- package/dist/features/categories/components/CategoryProductsView.d.ts +0 -45
- package/dist/features/categories/components/CategoryProductsView.js +0 -21
- package/dist/features/homepage/components/HowItWorksInfoView.d.ts +0 -36
- package/dist/features/homepage/components/HowItWorksInfoView.js +0 -10
- package/dist/features/layout/BottomActions.d.ts +0 -1
- package/dist/features/layout/BottomActions.js +0 -311
- package/dist/features/products/components/ProductsView.d.ts +0 -6
- package/dist/features/products/components/ProductsView.js +0 -5
- package/dist/features/seller/components/SellerStoreView.d.ts +0 -8
- package/dist/features/seller/components/SellerStoreView.js +0 -8
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
import type { SearchIndexArtifact } from "../../../../shared/search/index-record";
|
|
2
|
+
import type { Firestore } from "firebase-admin/firestore";
|
|
3
|
+
import type { Bucket } from "@google-cloud/storage";
|
|
4
|
+
export declare const SEARCH_INDEX_COLLECTION = "searchIndex";
|
|
5
|
+
export declare const SEARCH_INDEX_MANIFEST_ID = "manifest";
|
|
6
|
+
/**
|
|
7
|
+
* 🛑 A declared ceiling, and the build FAILS rather than truncating.
|
|
8
|
+
*
|
|
9
|
+
* Deliberately the opposite of `buildSearchTxt`'s `MAX_TOKENS`, which this
|
|
10
|
+
* codebase complains about in four separate places for silently slicing. A
|
|
11
|
+
* short artifact is a search index missing rows, and nothing downstream can
|
|
12
|
+
* tell that from a small catalogue. 1.5 MB against a measured 384 KB leaves
|
|
13
|
+
* roughly 4x headroom; if it is ever hit, that is a real signal.
|
|
14
|
+
*/
|
|
15
|
+
export declare const SEARCH_INDEX_MAX_BYTES = 1500000;
|
|
16
|
+
export interface SearchIndexManifest {
|
|
17
|
+
schema: number;
|
|
18
|
+
version: number;
|
|
19
|
+
builtAt: string;
|
|
20
|
+
lastFullAt: string | null;
|
|
21
|
+
storagePath: string;
|
|
22
|
+
recordCount: number;
|
|
23
|
+
bytes: number;
|
|
24
|
+
perType: Record<string, number>;
|
|
25
|
+
/** Set by the content triggers; cleared when a build consumes it. */
|
|
26
|
+
dirty: boolean;
|
|
27
|
+
}
|
|
28
|
+
type ManifestDb = Firestore;
|
|
29
|
+
type StorageBucket = Bucket;
|
|
30
|
+
export declare function manifestRef(db: ManifestDb): FirebaseFirestore.DocumentReference<FirebaseFirestore.DocumentData, FirebaseFirestore.DocumentData>;
|
|
31
|
+
export declare function readManifest(db: ManifestDb): Promise<SearchIndexManifest | null>;
|
|
32
|
+
/**
|
|
33
|
+
* Mark the index stale. Called from the content triggers.
|
|
34
|
+
*
|
|
35
|
+
* 🛑 READ-THEN-WRITE, guarding on a BOOLEAN — never a deep compare.
|
|
36
|
+
* Root Cause #92's trigger cost 12 million invocations a day because its
|
|
37
|
+
* no-op guard used `JSON.stringify`, which is key-order sensitive and so
|
|
38
|
+
* never matched. A boolean cannot have that failure mode, and the read means
|
|
39
|
+
* a burst of 50 product writes costs 50 reads and exactly ONE write.
|
|
40
|
+
*
|
|
41
|
+
* Never throws: a failed dirty-mark delays the index by one cadence, and
|
|
42
|
+
* failing the product write that triggered it would be far worse.
|
|
43
|
+
*/
|
|
44
|
+
export declare function markSearchIndexDirty(db: ManifestDb, job: string): Promise<void>;
|
|
45
|
+
export declare function storagePathFor(version: number): string;
|
|
46
|
+
export interface PublishResult {
|
|
47
|
+
storagePath: string;
|
|
48
|
+
bytes: number;
|
|
49
|
+
pruned: number;
|
|
50
|
+
}
|
|
51
|
+
/**
|
|
52
|
+
* Write the artifact and prune older versions.
|
|
53
|
+
*
|
|
54
|
+
* The object carries `public, max-age=31536000, immutable` because the URL
|
|
55
|
+
* contains the version — a new build is a new URL, so nothing ever needs
|
|
56
|
+
* revalidating.
|
|
57
|
+
*/
|
|
58
|
+
export declare function publishArtifact(bucket: StorageBucket, artifact: SearchIndexArtifact): Promise<PublishResult>;
|
|
59
|
+
/**
|
|
60
|
+
* Mirror the manifest to Storage so the client needs no Firebase SDK.
|
|
61
|
+
*
|
|
62
|
+
* Writes only the four fields a browser has any business reading — `dirty`,
|
|
63
|
+
* `lastFullAt` and `perType` are build bookkeeping, and publishing "the index
|
|
64
|
+
* is currently stale" to every visitor tells them nothing they can act on.
|
|
65
|
+
* The shape is `SearchIndexManifestPublic`, declared beside the URL builders.
|
|
66
|
+
*
|
|
67
|
+
* ## 🛑 60 seconds, and the number is load-bearing in both directions
|
|
68
|
+
*
|
|
69
|
+
* This is the ONLY uncacheable-for-long hop in the whole design, so it sets
|
|
70
|
+
* the floor on how fast a new build reaches a visitor — and the ceiling on
|
|
71
|
+
* how often a returning visitor pays for a request. The artifact behind it is
|
|
72
|
+
* `immutable`, so a client that re-reads the manifest and finds the same
|
|
73
|
+
* version does one small conditional fetch and no download at all.
|
|
74
|
+
*
|
|
75
|
+
* Too long and the 15-minute build cadence is a lie; too short and every
|
|
76
|
+
* visitor re-requests it per page view for a document that changes a few
|
|
77
|
+
* times an hour.
|
|
78
|
+
*
|
|
79
|
+
* ## Written LAST, and failing it does not fail the build
|
|
80
|
+
*
|
|
81
|
+
* A published artifact with a stale manifest serves the previous index for up
|
|
82
|
+
* to a minute — correct, just behind. A manifest pointing at an artifact that
|
|
83
|
+
* was never written is a 404 on the client's critical path. So the order is
|
|
84
|
+
* artifact, then Firestore, then this; and a throw here is logged, not
|
|
85
|
+
* propagated, because the Firestore manifest (which is what the NEXT build
|
|
86
|
+
* reads) is already correct by this point.
|
|
87
|
+
*/
|
|
88
|
+
export declare function publishManifestMirror(bucket: StorageBucket, manifest: SearchIndexManifest): Promise<void>;
|
|
89
|
+
export {};
|
|
@@ -0,0 +1,208 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* WHY: Where the search artifact lives, and how it is served without a
|
|
3
|
+
* Next.js route in front of it.
|
|
4
|
+
* WHAT: The Firestore manifest singleton, the Storage upload, and the prune.
|
|
5
|
+
*
|
|
6
|
+
* ## Why Storage directly rather than an /api route
|
|
7
|
+
*
|
|
8
|
+
* A version-addressed object written with `immutable` cache headers is served
|
|
9
|
+
* by the browser and any CDN in front of the bucket with no function in the
|
|
10
|
+
* path at all. `storage.rules` is already `allow read: if true` for every
|
|
11
|
+
* object, so no rules change is needed.
|
|
12
|
+
*
|
|
13
|
+
* It is also the only shape that can be deployed AND verified under this
|
|
14
|
+
* round's constraint of no Vercel deploy: the Function writes the artifact and
|
|
15
|
+
* it is immediately fetchable. A same-origin `/api/search-index/*` proxy stays
|
|
16
|
+
* a documented fallback if CORS or egress accounting later argues for it — it
|
|
17
|
+
* would change the two URLs and nothing else.
|
|
18
|
+
*
|
|
19
|
+
* ## 🛑 Old versions MUST be pruned
|
|
20
|
+
*
|
|
21
|
+
* Immutable version-addressed objects never overwrite, so without a prune this
|
|
22
|
+
* accumulates ~384 KB per content change, for ever, against a ~5 GB allowance
|
|
23
|
+
* the media library already shares. Two versions are kept — the current one
|
|
24
|
+
* and the one it replaced — so a client that fetched mid-publish is never
|
|
25
|
+
* orphaned, matching the service worker's `maxEntries: 2`.
|
|
26
|
+
*
|
|
27
|
+
* @tag domain:search
|
|
28
|
+
* @tag layer:server
|
|
29
|
+
* @tag pattern:none
|
|
30
|
+
* @tag access:server
|
|
31
|
+
* @tag consumers:searchIndexBuild
|
|
32
|
+
* @tag sideEffects:firestore,storage
|
|
33
|
+
*/
|
|
34
|
+
import { normalizeError } from "../../../../../errors/normalize.js";
|
|
35
|
+
import { serverLogger } from "../../../../../monitoring/server-logger.js";
|
|
36
|
+
import { SEARCH_INDEX_PREFIX, SEARCH_MANIFEST_OBJECT, searchIndexObjectPath, } from "../../../../shared/search/artifact-url.js";
|
|
37
|
+
export const SEARCH_INDEX_COLLECTION = "searchIndex";
|
|
38
|
+
export const SEARCH_INDEX_MANIFEST_ID = "manifest";
|
|
39
|
+
/*
|
|
40
|
+
* 🛑 `SEARCH_INDEX_PREFIX` and the object paths are IMPORTED, not declared
|
|
41
|
+
* here. They were declared here first, and the client then needed the same
|
|
42
|
+
* two strings — which is a mirror, and a mirror of a URL fails in the worst
|
|
43
|
+
* available way: the builder writes `search-index/v4.json`, the client asks
|
|
44
|
+
* for `searchIndex/v4.json`, and a 404 is indistinguishable from "the index
|
|
45
|
+
* has not been built yet" (Root Cause #42). One declaration, in the shared
|
|
46
|
+
* module both sides already import.
|
|
47
|
+
*/
|
|
48
|
+
/** Current + previous. See the prune note in the header. */
|
|
49
|
+
const KEEP_VERSIONS = 2;
|
|
50
|
+
/**
|
|
51
|
+
* 🛑 A declared ceiling, and the build FAILS rather than truncating.
|
|
52
|
+
*
|
|
53
|
+
* Deliberately the opposite of `buildSearchTxt`'s `MAX_TOKENS`, which this
|
|
54
|
+
* codebase complains about in four separate places for silently slicing. A
|
|
55
|
+
* short artifact is a search index missing rows, and nothing downstream can
|
|
56
|
+
* tell that from a small catalogue. 1.5 MB against a measured 384 KB leaves
|
|
57
|
+
* roughly 4x headroom; if it is ever hit, that is a real signal.
|
|
58
|
+
*/
|
|
59
|
+
export const SEARCH_INDEX_MAX_BYTES = 1500000;
|
|
60
|
+
export function manifestRef(db) {
|
|
61
|
+
return db.collection(SEARCH_INDEX_COLLECTION).doc(SEARCH_INDEX_MANIFEST_ID);
|
|
62
|
+
}
|
|
63
|
+
export async function readManifest(db) {
|
|
64
|
+
const snap = await manifestRef(db).get();
|
|
65
|
+
if (!snap.exists)
|
|
66
|
+
return null;
|
|
67
|
+
/*
|
|
68
|
+
* Firestore hands back `DocumentData`, which is `Record<string, any>` — the
|
|
69
|
+
* one place a cast is unavoidable, because the SDK cannot know the shape.
|
|
70
|
+
* Narrowed to the declared interface at the single read site rather than
|
|
71
|
+
* left as `DocumentData` for callers to re-cast: the latter is how a
|
|
72
|
+
* boundary type spreads (Root Cause #98).
|
|
73
|
+
*/
|
|
74
|
+
return snap.data() ?? null;
|
|
75
|
+
}
|
|
76
|
+
/**
|
|
77
|
+
* Mark the index stale. Called from the content triggers.
|
|
78
|
+
*
|
|
79
|
+
* 🛑 READ-THEN-WRITE, guarding on a BOOLEAN — never a deep compare.
|
|
80
|
+
* Root Cause #92's trigger cost 12 million invocations a day because its
|
|
81
|
+
* no-op guard used `JSON.stringify`, which is key-order sensitive and so
|
|
82
|
+
* never matched. A boolean cannot have that failure mode, and the read means
|
|
83
|
+
* a burst of 50 product writes costs 50 reads and exactly ONE write.
|
|
84
|
+
*
|
|
85
|
+
* Never throws: a failed dirty-mark delays the index by one cadence, and
|
|
86
|
+
* failing the product write that triggered it would be far worse.
|
|
87
|
+
*/
|
|
88
|
+
export async function markSearchIndexDirty(db, job) {
|
|
89
|
+
try {
|
|
90
|
+
const ref = manifestRef(db);
|
|
91
|
+
const snap = await ref.get();
|
|
92
|
+
if (snap.exists && snap.data()?.dirty === true)
|
|
93
|
+
return;
|
|
94
|
+
await ref.set({ dirty: true }, { merge: true });
|
|
95
|
+
}
|
|
96
|
+
catch (err) {
|
|
97
|
+
const e = normalizeError(err);
|
|
98
|
+
serverLogger.warn("markSearchIndexDirty failed (non-fatal)", { job, message: e.message });
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
export function storagePathFor(version) {
|
|
102
|
+
return searchIndexObjectPath(version);
|
|
103
|
+
}
|
|
104
|
+
/**
|
|
105
|
+
* Write the artifact and prune older versions.
|
|
106
|
+
*
|
|
107
|
+
* The object carries `public, max-age=31536000, immutable` because the URL
|
|
108
|
+
* contains the version — a new build is a new URL, so nothing ever needs
|
|
109
|
+
* revalidating.
|
|
110
|
+
*/
|
|
111
|
+
export async function publishArtifact(bucket, artifact) {
|
|
112
|
+
const body = Buffer.from(JSON.stringify(artifact), "utf8");
|
|
113
|
+
if (body.byteLength > SEARCH_INDEX_MAX_BYTES) {
|
|
114
|
+
// Loud, not truncated. See SEARCH_INDEX_MAX_BYTES.
|
|
115
|
+
throw new Error(`search index artifact is ${body.byteLength} bytes, over the declared ` +
|
|
116
|
+
`${SEARCH_INDEX_MAX_BYTES}. Refusing to publish a silently short index — ` +
|
|
117
|
+
`raise the ceiling deliberately or find out why it grew.`);
|
|
118
|
+
}
|
|
119
|
+
const path = storagePathFor(artifact.version);
|
|
120
|
+
await bucket.file(path).save(body, {
|
|
121
|
+
contentType: "application/json; charset=utf-8",
|
|
122
|
+
metadata: {
|
|
123
|
+
cacheControl: "public, max-age=31536000, immutable",
|
|
124
|
+
contentEncoding: undefined,
|
|
125
|
+
},
|
|
126
|
+
});
|
|
127
|
+
let pruned = 0;
|
|
128
|
+
try {
|
|
129
|
+
const [files] = await bucket.getFiles({ prefix: `${SEARCH_INDEX_PREFIX}/v` });
|
|
130
|
+
const versions = files
|
|
131
|
+
.map((f) => ({ name: f.name, v: Number(/v(\d+)\.json$/.exec(f.name)?.[1] ?? NaN) }))
|
|
132
|
+
.filter((f) => Number.isFinite(f.v))
|
|
133
|
+
.sort((a, b) => b.v - a.v);
|
|
134
|
+
for (const stale of versions.slice(KEEP_VERSIONS)) {
|
|
135
|
+
await bucket.file(stale.name).delete();
|
|
136
|
+
pruned++;
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
catch (err) {
|
|
140
|
+
/*
|
|
141
|
+
* A failed prune is NOT a failed publish. The new artifact is already
|
|
142
|
+
* live and correct; the cost of leaving an extra 384 KB object behind is
|
|
143
|
+
* trivial next to failing a build that produced a good index.
|
|
144
|
+
*/
|
|
145
|
+
const e = normalizeError(err);
|
|
146
|
+
serverLogger.warn("search index prune failed (artifact is published)", {
|
|
147
|
+
message: e.message,
|
|
148
|
+
});
|
|
149
|
+
}
|
|
150
|
+
return { storagePath: path, bytes: body.byteLength, pruned };
|
|
151
|
+
}
|
|
152
|
+
/**
|
|
153
|
+
* Mirror the manifest to Storage so the client needs no Firebase SDK.
|
|
154
|
+
*
|
|
155
|
+
* Writes only the four fields a browser has any business reading — `dirty`,
|
|
156
|
+
* `lastFullAt` and `perType` are build bookkeeping, and publishing "the index
|
|
157
|
+
* is currently stale" to every visitor tells them nothing they can act on.
|
|
158
|
+
* The shape is `SearchIndexManifestPublic`, declared beside the URL builders.
|
|
159
|
+
*
|
|
160
|
+
* ## 🛑 60 seconds, and the number is load-bearing in both directions
|
|
161
|
+
*
|
|
162
|
+
* This is the ONLY uncacheable-for-long hop in the whole design, so it sets
|
|
163
|
+
* the floor on how fast a new build reaches a visitor — and the ceiling on
|
|
164
|
+
* how often a returning visitor pays for a request. The artifact behind it is
|
|
165
|
+
* `immutable`, so a client that re-reads the manifest and finds the same
|
|
166
|
+
* version does one small conditional fetch and no download at all.
|
|
167
|
+
*
|
|
168
|
+
* Too long and the 15-minute build cadence is a lie; too short and every
|
|
169
|
+
* visitor re-requests it per page view for a document that changes a few
|
|
170
|
+
* times an hour.
|
|
171
|
+
*
|
|
172
|
+
* ## Written LAST, and failing it does not fail the build
|
|
173
|
+
*
|
|
174
|
+
* A published artifact with a stale manifest serves the previous index for up
|
|
175
|
+
* to a minute — correct, just behind. A manifest pointing at an artifact that
|
|
176
|
+
* was never written is a 404 on the client's critical path. So the order is
|
|
177
|
+
* artifact, then Firestore, then this; and a throw here is logged, not
|
|
178
|
+
* propagated, because the Firestore manifest (which is what the NEXT build
|
|
179
|
+
* reads) is already correct by this point.
|
|
180
|
+
*/
|
|
181
|
+
export async function publishManifestMirror(bucket, manifest) {
|
|
182
|
+
try {
|
|
183
|
+
const body = Buffer.from(JSON.stringify({
|
|
184
|
+
schema: manifest.schema,
|
|
185
|
+
version: manifest.version,
|
|
186
|
+
builtAt: manifest.builtAt,
|
|
187
|
+
recordCount: manifest.recordCount,
|
|
188
|
+
}), "utf8");
|
|
189
|
+
await bucket.file(SEARCH_MANIFEST_OBJECT).save(body, {
|
|
190
|
+
contentType: "application/json; charset=utf-8",
|
|
191
|
+
metadata: { cacheControl: "public, max-age=60" },
|
|
192
|
+
});
|
|
193
|
+
}
|
|
194
|
+
catch (err) {
|
|
195
|
+
const e = normalizeError(err);
|
|
196
|
+
/*
|
|
197
|
+
* WARN rather than throw. But it is a real symptom: the artifact is live
|
|
198
|
+
* and no client will ever learn its version, so the index silently stops
|
|
199
|
+
* advancing while every other signal says the build succeeded. That is
|
|
200
|
+
* precisely the "confident wrong answer" shape, which is why it is logged
|
|
201
|
+
* at WARN and not swallowed.
|
|
202
|
+
*/
|
|
203
|
+
serverLogger.warn("search index manifest mirror failed (artifact is published)", {
|
|
204
|
+
version: manifest.version,
|
|
205
|
+
message: e.message,
|
|
206
|
+
});
|
|
207
|
+
}
|
|
208
|
+
}
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
import { type IndexDocData } from "./sources";
|
|
2
|
+
import { type SearchIndexArtifact, type SearchIndexRecord } from "../../../../shared/search/index-record";
|
|
3
|
+
/** The minimal Firestore surface this module needs, so it stays testable. */
|
|
4
|
+
interface BuildQuery {
|
|
5
|
+
where(field: string, op: string, value: unknown): BuildQuery;
|
|
6
|
+
select(...fields: string[]): BuildQuery;
|
|
7
|
+
get(): Promise<{
|
|
8
|
+
docs: Array<{
|
|
9
|
+
id: string;
|
|
10
|
+
data(): IndexDocData;
|
|
11
|
+
}>;
|
|
12
|
+
}>;
|
|
13
|
+
}
|
|
14
|
+
interface BuildDb {
|
|
15
|
+
collection(name: string): BuildQuery;
|
|
16
|
+
}
|
|
17
|
+
export interface BuildStats {
|
|
18
|
+
readonly mode: "full" | "incremental";
|
|
19
|
+
/** Documents READ. This is the number that has to stay inside the budget. */
|
|
20
|
+
readonly reads: number;
|
|
21
|
+
readonly perType: Readonly<Record<string, number>>;
|
|
22
|
+
/** Documents read but rejected by `toRecord` — unpublished, unjoinable, … */
|
|
23
|
+
readonly dropped: number;
|
|
24
|
+
/**
|
|
25
|
+
* Sources that threw. Non-zero means the artifact is INCOMPLETE for those
|
|
26
|
+
* types, so the caller must not treat the build as clean — see the
|
|
27
|
+
* `dirty` handling in `searchIndexBuild`.
|
|
28
|
+
*/
|
|
29
|
+
readonly failed: number;
|
|
30
|
+
readonly durationMs: number;
|
|
31
|
+
}
|
|
32
|
+
export interface BuildResult {
|
|
33
|
+
readonly artifact: SearchIndexArtifact;
|
|
34
|
+
readonly stats: BuildStats;
|
|
35
|
+
}
|
|
36
|
+
/**
|
|
37
|
+
* Build from scratch. Reconciles hard deletes by construction, because the
|
|
38
|
+
* result contains exactly what the sources currently return.
|
|
39
|
+
*/
|
|
40
|
+
export declare function buildFull(db: BuildDb, version: number, now?: Date): Promise<BuildResult>;
|
|
41
|
+
/**
|
|
42
|
+
* Patch the previous artifact with everything that changed since it was built.
|
|
43
|
+
*
|
|
44
|
+
* 🛑 A source that fails here is logged and SKIPPED, not fatal — unlike the
|
|
45
|
+
* full build. The difference is what the failure costs: a skipped incremental
|
|
46
|
+
* source leaves that type's records at their previous (correct, slightly
|
|
47
|
+
* stale) values and the next pass picks them up, whereas a skipped full source
|
|
48
|
+
* would delete the type from the artifact entirely.
|
|
49
|
+
*/
|
|
50
|
+
export declare function buildIncremental(db: BuildDb, previous: SearchIndexArtifact, since: Date, version: number, now?: Date): Promise<BuildResult>;
|
|
51
|
+
/**
|
|
52
|
+
* Re-key a record from a previous artifact. Mirrors `keyOf` — both sides key
|
|
53
|
+
* on `${typeKey}:${slug}`, which is the invariant that makes an incremental
|
|
54
|
+
* patch REPLACE a changed row rather than add a second copy of it.
|
|
55
|
+
*/
|
|
56
|
+
export declare function keyFromRecord(rec: SearchIndexRecord): string | null;
|
|
57
|
+
export {};
|
|
@@ -0,0 +1,285 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* WHY: Turn the public collections into one small artifact the browser can
|
|
3
|
+
* search locally, WITHOUT the read cost that makes the obvious version
|
|
4
|
+
* impossible.
|
|
5
|
+
* WHAT: Full and incremental builds, plus the stats a caller must log.
|
|
6
|
+
*
|
|
7
|
+
* ## 🛑 THE READ BUDGET IS THE DESIGN. Read this before changing the cadence.
|
|
8
|
+
*
|
|
9
|
+
* Firestore bills **per document read regardless of `select()`** — projection
|
|
10
|
+
* cuts egress, not read count. So the naive version, a full rebuild every 15
|
|
11
|
+
* minutes, is `96 × 3,730 ≈ 358,000 reads/day` against a **50,000/day** free
|
|
12
|
+
* tier. Seven times over, for a feature nobody asked to be expensive. Hourly
|
|
13
|
+
* is still ~89,000.
|
|
14
|
+
*
|
|
15
|
+
* Hence two modes:
|
|
16
|
+
*
|
|
17
|
+
* idle manifest says not dirty 1 read ~90/day
|
|
18
|
+
* incremental `updatedAt > lastBuiltAt` per source 13 + changed ~200/day
|
|
19
|
+
* full everything; reconciles hard deletes 3,730 3,730/day
|
|
20
|
+
*
|
|
21
|
+
* ≈ 4,000 reads/day, about 8% of the tier.
|
|
22
|
+
*
|
|
23
|
+
* 🛑 THE INCREMENTAL CLAUSE NEEDS A COMPOSITE INDEX PER SOURCE, and an earlier
|
|
24
|
+
* version of this header claimed the opposite.
|
|
25
|
+
*
|
|
26
|
+
* A LONE range clause on `updatedAt` needs nothing — Firestore auto-creates
|
|
27
|
+
* single-field ascending indexes. But every source that also carries a
|
|
28
|
+
* `where` equality (`status == published`, `categoryType == brand`,
|
|
29
|
+
* `isActive == true`) combines equality + range, and that DOES require a
|
|
30
|
+
* composite. Only `category`, `part` and `model` — the three with no `where`
|
|
31
|
+
* — are index-free.
|
|
32
|
+
*
|
|
33
|
+
* Found by running the job against production, not by reading it: the FULL
|
|
34
|
+
* build has no range clause and passed cleanly, so nothing surfaced until the
|
|
35
|
+
* first INCREMENTAL pass returned `9 FAILED_PRECONDITION` for brand, store,
|
|
36
|
+
* blog and every other filtered source. Exactly the Root Cause #59 shape the
|
|
37
|
+
* old comment claimed immunity from.
|
|
38
|
+
*
|
|
39
|
+
* The indexes are declared in `appkit/firebase/base/firestore.indexes.json`.
|
|
40
|
+
* Adding a source with a `where` means adding its `(field, updatedAt)` pair
|
|
41
|
+
* there too, or that source silently stops updating incrementally.
|
|
42
|
+
*
|
|
43
|
+
* ## Unpublish vs hard delete — different mechanisms, say which is which
|
|
44
|
+
*
|
|
45
|
+
* **Unpublish is handled incrementally.** A product flipped `published ->
|
|
46
|
+
* draft` still has a fresh `updatedAt`, so the builder re-reads it, finds it
|
|
47
|
+
* fails the source's filter, and REMOVES it from the artifact within the
|
|
48
|
+
* cadence. That is the case that matters, because it is the one that happens.
|
|
49
|
+
*
|
|
50
|
+
* **A hard delete is invisible to an `updatedAt` query** and is reconciled by
|
|
51
|
+
* the daily full pass. Worst case is a dead link for up to 24 h, which is link
|
|
52
|
+
* rot rather than disclosure. Tombstone machinery would be real complexity for
|
|
53
|
+
* the rarer half of a split that can be described in two sentences.
|
|
54
|
+
*
|
|
55
|
+
* @tag domain:search
|
|
56
|
+
* @tag layer:server
|
|
57
|
+
* @tag pattern:none
|
|
58
|
+
* @tag access:server
|
|
59
|
+
* @tag consumers:searchIndexBuild
|
|
60
|
+
* @tag sideEffects:firestore-read
|
|
61
|
+
*/
|
|
62
|
+
import { normalizeError } from "../../../../../errors/normalize.js";
|
|
63
|
+
import { serverLogger } from "../../../../../monitoring/server-logger.js";
|
|
64
|
+
import { hidePublicTestData } from "../../tester/visibility.js";
|
|
65
|
+
import { INDEX_TYPES, PUBLIC_INDEX_SOURCES, typeIndexFor, } from "./sources.js";
|
|
66
|
+
import { INDEX_UPDATED_AT_FIELD, SEARCH_INDEX_SCHEMA_VERSION, } from "../../../../shared/search/index-record.js";
|
|
67
|
+
/**
|
|
68
|
+
* The patch key, derived from the source's own `slugOf` so the REMOVE path and
|
|
69
|
+
* the ADD path cannot disagree — and so re-keying a previous artifact (which
|
|
70
|
+
* has only the slug) lands on the same string.
|
|
71
|
+
*/
|
|
72
|
+
function typeKeyFor(source, d) {
|
|
73
|
+
return source.typeKeyOf ? source.typeKeyOf(d) : source.typeKey;
|
|
74
|
+
}
|
|
75
|
+
function keyOf(source, id, d) {
|
|
76
|
+
const slug = source.slugOf(id, d);
|
|
77
|
+
return slug ? `${typeKeyFor(source, d)}:${slug}` : null;
|
|
78
|
+
}
|
|
79
|
+
async function readSource(db, source, since) {
|
|
80
|
+
let q = db.collection(source.collection);
|
|
81
|
+
for (const [field, value] of source.where ?? [])
|
|
82
|
+
q = q.where(field, "==", value);
|
|
83
|
+
/*
|
|
84
|
+
* 🛑 The incremental clause is applied INSTEAD OF nothing, not in addition
|
|
85
|
+
* to a page limit. An unbounded `updatedAt >` on a quiet collection returns
|
|
86
|
+
* a handful of rows; on a reseed it returns everything, which is correct —
|
|
87
|
+
* a reseed genuinely did change everything.
|
|
88
|
+
*/
|
|
89
|
+
if (since)
|
|
90
|
+
q = q.where(INDEX_UPDATED_AT_FIELD, ">", since);
|
|
91
|
+
const snap = await q.select(...source.select, "isTestData", INDEX_UPDATED_AT_FIELD).get();
|
|
92
|
+
/*
|
|
93
|
+
* Firestore bills a minimum of one read for a query that matches nothing,
|
|
94
|
+
* so an idle incremental pass still costs one per source. Counting it is
|
|
95
|
+
* the difference between a budget and a guess.
|
|
96
|
+
*/
|
|
97
|
+
return { docs: snap.docs, reads: Math.max(1, snap.docs.length) };
|
|
98
|
+
}
|
|
99
|
+
function toRecords(source, docs) {
|
|
100
|
+
const kept = [];
|
|
101
|
+
const removed = [];
|
|
102
|
+
let dropped = 0;
|
|
103
|
+
/*
|
|
104
|
+
* 🛑 `hidePublicTestData` is NOT optional here, and this call site differs
|
|
105
|
+
* from every other one. Elsewhere a sandbox row that slips through is shown
|
|
106
|
+
* to one viewer on one render. Here it is baked into a shared, immutable,
|
|
107
|
+
* CDN-cached body and published to everyone for the life of that version.
|
|
108
|
+
*/
|
|
109
|
+
/*
|
|
110
|
+
* 🛑 An INTERSECTION, not a cast. `IndexDocData` is `Record<string,
|
|
111
|
+
* unknown>`, whose index signature makes `isTestData` an `unknown` and so
|
|
112
|
+
* fails `TestDataFlagged`. The lazy fix is `as any`, which would also
|
|
113
|
+
* silence the fact that `hidePublicTestData` is generic and preserves the
|
|
114
|
+
* row type — including `__id`, which the loop below needs. Naming the shape
|
|
115
|
+
* keeps both ends type-checked.
|
|
116
|
+
*/
|
|
117
|
+
const rows = docs.map((d) => ({ ...d.data(), __id: d.id }));
|
|
118
|
+
const visible = hidePublicTestData(rows);
|
|
119
|
+
for (const row of visible) {
|
|
120
|
+
const id = row.__id;
|
|
121
|
+
const key = keyOf(source, id, row);
|
|
122
|
+
// No derivable slug means no href and no stable identity — such a row
|
|
123
|
+
// cannot be a search result at all, so it is dropped rather than keyed
|
|
124
|
+
// under something invented.
|
|
125
|
+
if (!key) {
|
|
126
|
+
dropped++;
|
|
127
|
+
continue;
|
|
128
|
+
}
|
|
129
|
+
const rec = source.toRecord(id, row);
|
|
130
|
+
if (!rec) {
|
|
131
|
+
dropped++;
|
|
132
|
+
// A document that fails `toRecord` on an INCREMENTAL pass is usually a
|
|
133
|
+
// row that just became non-public — so it must be removed, not ignored.
|
|
134
|
+
removed.push(key);
|
|
135
|
+
continue;
|
|
136
|
+
}
|
|
137
|
+
/*
|
|
138
|
+
* 🛑 Resolved per DOCUMENT, not per source. `products` holds all nine
|
|
139
|
+
* listing types and each has its own detail route, so a single type index
|
|
140
|
+
* for the collection would send every auction to /products/{slug} —
|
|
141
|
+
* Root Cause #48, rebuilt.
|
|
142
|
+
*/
|
|
143
|
+
const typeKey = typeKeyFor(source, row);
|
|
144
|
+
const typeIdx = typeIndexFor(typeKey);
|
|
145
|
+
if (typeIdx < 0) {
|
|
146
|
+
dropped++;
|
|
147
|
+
continue;
|
|
148
|
+
}
|
|
149
|
+
const slug = key.slice(typeKey.length + 1);
|
|
150
|
+
const tuple = rec.facets === undefined
|
|
151
|
+
? [typeIdx, rec.title, rec.detail, slug]
|
|
152
|
+
: [typeIdx, rec.title, rec.detail, slug, rec.facets];
|
|
153
|
+
kept.push([key, tuple]);
|
|
154
|
+
}
|
|
155
|
+
return { kept, removed, dropped };
|
|
156
|
+
}
|
|
157
|
+
function envelope(version, keyed, builtAt) {
|
|
158
|
+
return {
|
|
159
|
+
schema: SEARCH_INDEX_SCHEMA_VERSION,
|
|
160
|
+
version,
|
|
161
|
+
builtAt: builtAt.toISOString(),
|
|
162
|
+
types: INDEX_TYPES,
|
|
163
|
+
// Sorted by key so two builds over identical data produce identical bytes,
|
|
164
|
+
// which is what makes "did anything actually change" answerable.
|
|
165
|
+
r: Object.keys(keyed)
|
|
166
|
+
.sort()
|
|
167
|
+
.map((k) => keyed[k]),
|
|
168
|
+
};
|
|
169
|
+
}
|
|
170
|
+
/**
|
|
171
|
+
* Build from scratch. Reconciles hard deletes by construction, because the
|
|
172
|
+
* result contains exactly what the sources currently return.
|
|
173
|
+
*/
|
|
174
|
+
export async function buildFull(db, version, now = new Date()) {
|
|
175
|
+
const started = Date.now();
|
|
176
|
+
const keyed = {};
|
|
177
|
+
const perType = {};
|
|
178
|
+
let reads = 0;
|
|
179
|
+
let dropped = 0;
|
|
180
|
+
for (const source of PUBLIC_INDEX_SOURCES) {
|
|
181
|
+
try {
|
|
182
|
+
const { docs, reads: r } = await readSource(db, source, null);
|
|
183
|
+
reads += r;
|
|
184
|
+
const out = toRecords(source, docs);
|
|
185
|
+
for (const [k, rec] of out.kept) {
|
|
186
|
+
keyed[k] = rec;
|
|
187
|
+
const tk = INDEX_TYPES[rec[0]]?.key ?? source.typeKey;
|
|
188
|
+
perType[tk] = (perType[tk] ?? 0) + 1;
|
|
189
|
+
}
|
|
190
|
+
dropped += out.dropped;
|
|
191
|
+
}
|
|
192
|
+
catch (err) {
|
|
193
|
+
const e = normalizeError(err);
|
|
194
|
+
/*
|
|
195
|
+
* 🛑 A failing source does NOT silently shrink the artifact. A full
|
|
196
|
+
* build that quietly omits `products` would publish a search index
|
|
197
|
+
* missing a third of the site and report success — the exact shape of
|
|
198
|
+
* every silent-degradation bug this codebase keeps recording.
|
|
199
|
+
*/
|
|
200
|
+
serverLogger.error("searchIndexBuild: source failed during FULL build — aborting", {
|
|
201
|
+
source: source.typeKey,
|
|
202
|
+
collection: source.collection,
|
|
203
|
+
error: e.message,
|
|
204
|
+
});
|
|
205
|
+
throw err;
|
|
206
|
+
}
|
|
207
|
+
}
|
|
208
|
+
return {
|
|
209
|
+
artifact: envelope(version, keyed, now),
|
|
210
|
+
stats: {
|
|
211
|
+
mode: "full",
|
|
212
|
+
reads,
|
|
213
|
+
perType,
|
|
214
|
+
dropped,
|
|
215
|
+
failed: 0,
|
|
216
|
+
durationMs: Date.now() - started,
|
|
217
|
+
},
|
|
218
|
+
};
|
|
219
|
+
}
|
|
220
|
+
/**
|
|
221
|
+
* Patch the previous artifact with everything that changed since it was built.
|
|
222
|
+
*
|
|
223
|
+
* 🛑 A source that fails here is logged and SKIPPED, not fatal — unlike the
|
|
224
|
+
* full build. The difference is what the failure costs: a skipped incremental
|
|
225
|
+
* source leaves that type's records at their previous (correct, slightly
|
|
226
|
+
* stale) values and the next pass picks them up, whereas a skipped full source
|
|
227
|
+
* would delete the type from the artifact entirely.
|
|
228
|
+
*/
|
|
229
|
+
export async function buildIncremental(db, previous, since, version, now = new Date()) {
|
|
230
|
+
const started = Date.now();
|
|
231
|
+
// Re-key the previous artifact. The tuple carries no id, so the key is
|
|
232
|
+
// rebuilt from type + slug — which is why `slug` must be stable per record.
|
|
233
|
+
const keyed = {};
|
|
234
|
+
for (const rec of previous.r) {
|
|
235
|
+
const key = keyFromRecord(rec);
|
|
236
|
+
if (key)
|
|
237
|
+
keyed[key] = rec;
|
|
238
|
+
}
|
|
239
|
+
const perType = {};
|
|
240
|
+
let reads = 0;
|
|
241
|
+
let dropped = 0;
|
|
242
|
+
let failed = 0;
|
|
243
|
+
for (const source of PUBLIC_INDEX_SOURCES) {
|
|
244
|
+
try {
|
|
245
|
+
const { docs, reads: r } = await readSource(db, source, since);
|
|
246
|
+
reads += r;
|
|
247
|
+
const out = toRecords(source, docs);
|
|
248
|
+
for (const k of out.removed)
|
|
249
|
+
delete keyed[k];
|
|
250
|
+
for (const [k, rec] of out.kept)
|
|
251
|
+
keyed[k] = rec;
|
|
252
|
+
perType[source.typeKey] = out.kept.length;
|
|
253
|
+
dropped += out.dropped;
|
|
254
|
+
}
|
|
255
|
+
catch (err) {
|
|
256
|
+
failed++;
|
|
257
|
+
const e = normalizeError(err);
|
|
258
|
+
serverLogger.warn("searchIndexBuild: source failed during INCREMENTAL build — skipped", {
|
|
259
|
+
source: source.typeKey,
|
|
260
|
+
collection: source.collection,
|
|
261
|
+
error: e.message,
|
|
262
|
+
});
|
|
263
|
+
}
|
|
264
|
+
}
|
|
265
|
+
return {
|
|
266
|
+
artifact: envelope(version, keyed, now),
|
|
267
|
+
stats: {
|
|
268
|
+
mode: "incremental",
|
|
269
|
+
reads,
|
|
270
|
+
perType,
|
|
271
|
+
dropped,
|
|
272
|
+
failed,
|
|
273
|
+
durationMs: Date.now() - started,
|
|
274
|
+
},
|
|
275
|
+
};
|
|
276
|
+
}
|
|
277
|
+
/**
|
|
278
|
+
* Re-key a record from a previous artifact. Mirrors `keyOf` — both sides key
|
|
279
|
+
* on `${typeKey}:${slug}`, which is the invariant that makes an incremental
|
|
280
|
+
* patch REPLACE a changed row rather than add a second copy of it.
|
|
281
|
+
*/
|
|
282
|
+
export function keyFromRecord(rec) {
|
|
283
|
+
const type = INDEX_TYPES[rec[0]];
|
|
284
|
+
return type ? `${type.key}:${rec[3]}` : null;
|
|
285
|
+
}
|