@mohasinac/appkit 4.43.0 → 4.44.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/_internal/server/features/categories/adapters.d.ts +47 -0
- package/dist/_internal/server/features/categories/adapters.js +235 -0
- package/dist/_internal/server/features/checkout/actions.js +161 -22
- package/dist/_internal/server/features/checkout/expand-order-items.js +34 -0
- package/dist/_internal/server/features/orders/adapters.js +21 -0
- package/dist/_internal/server/features/refunds/actions.js +1 -1
- package/dist/_internal/server/features/seo/sitemap.js +54 -15
- package/dist/_internal/server/features/stores/adapters.d.ts +1 -1
- package/dist/_internal/server/features/stores/adapters.js +6 -0
- package/dist/_internal/server/functions/scheduled.d.ts +4 -2
- package/dist/_internal/server/functions/scheduled.js +64 -6
- package/dist/_internal/server/jobs/core/adminAnalytics.d.ts +0 -1
- package/dist/_internal/server/jobs/core/adminAnalytics.js +9 -1
- package/dist/_internal/server/jobs/core/analyticsAlertEvaluate.d.ts +47 -0
- package/dist/_internal/server/jobs/core/analyticsAlertEvaluate.js +468 -0
- package/dist/_internal/server/jobs/core/autoPayoutEligibility.js +14 -1
- package/dist/_internal/server/jobs/core/countersReconcile.js +24 -8
- package/dist/_internal/server/jobs/core/onCategoryWrite.js +54 -0
- package/dist/_internal/server/jobs/core/priceIndexRollup.d.ts +47 -0
- package/dist/_internal/server/jobs/core/priceIndexRollup.js +234 -0
- package/dist/_internal/server/jobs/core/{testerSandboxCleanup.d.ts → testDataCleanup.d.ts} +3 -3
- package/dist/_internal/server/jobs/core/{testerSandboxCleanup.js → testDataCleanup.js} +3 -3
- package/dist/_internal/server/jobs/core/weeklyPayoutEligibility.js +5 -1
- package/dist/_internal/server/jobs/handlers/analyticsAlertEvaluate.d.ts +2 -0
- package/dist/_internal/server/jobs/handlers/analyticsAlertEvaluate.js +2 -0
- package/dist/_internal/server/jobs/handlers/index.d.ts +3 -1
- package/dist/_internal/server/jobs/handlers/index.js +3 -1
- package/dist/_internal/server/jobs/handlers/messages.d.ts +5 -2
- package/dist/_internal/server/jobs/handlers/messages.js +5 -2
- package/dist/_internal/server/jobs/handlers/priceIndexRollup.d.ts +2 -0
- package/dist/_internal/server/jobs/handlers/priceIndexRollup.js +2 -0
- package/dist/_internal/server/jobs/handlers/testDataCleanup.d.ts +2 -0
- package/dist/_internal/server/jobs/handlers/testDataCleanup.js +2 -0
- package/dist/_internal/shared/actions/action-registry.js +34 -0
- package/dist/_internal/shared/checkout/order-math.d.ts +113 -3
- package/dist/_internal/shared/checkout/order-math.js +194 -7
- package/dist/_internal/shared/features/categories/price-guidance.d.ts +110 -0
- package/dist/_internal/shared/features/categories/price-guidance.js +136 -0
- package/dist/_internal/shared/features/site-settings/gst-registration.d.ts +90 -0
- package/dist/_internal/shared/features/site-settings/gst-registration.js +126 -0
- package/dist/_internal/shared/fees/calculator.d.ts +105 -0
- package/dist/_internal/shared/fees/calculator.js +143 -0
- package/dist/_internal/shared/listing-types/art/config.d.ts +0 -6
- package/dist/_internal/shared/listing-types/classified/config.d.ts +0 -6
- package/dist/_internal/shared/listing-types/digital-code/config.d.ts +0 -6
- package/dist/_internal/shared/listing-types/live/config.d.ts +0 -6
- package/dist/_internal/shared/listing-types/standard/config.d.ts +0 -6
- package/dist/_internal/shared/listing-types/stickers/config.d.ts +0 -6
- package/dist/_internal/shared/templating/placeholders.d.ts +70 -0
- package/dist/_internal/shared/templating/placeholders.js +73 -0
- package/dist/client.d.ts +2 -0
- package/dist/client.js +1 -0
- package/dist/constants/api-endpoints.d.ts +42 -0
- package/dist/constants/api-endpoints.js +14 -0
- package/dist/features/admin/actions/admin-actions.d.ts +1 -1
- package/dist/features/admin/actions/admin-actions.js +1 -1
- package/dist/features/admin/components/AdminCategoryEditorView.js +269 -3
- package/dist/features/admin/components/AdminProductEditorView.js +8 -1
- package/dist/features/admin/components/AdminProductsView.js +78 -7
- package/dist/features/admin/components/AdminSiteSettingsView.js +20 -1
- package/dist/features/admin/components/AdminTaxCodeEditorView.d.ts +10 -0
- package/dist/features/admin/components/AdminTaxCodeEditorView.js +160 -0
- package/dist/features/admin/components/AdminTaxCodesView.d.ts +13 -0
- package/dist/features/admin/components/AdminTaxCodesView.js +86 -0
- package/dist/features/admin/components/AdminTesterChecklistView.js +1 -1
- package/dist/features/admin/components/analytics/AdminTopProductsTable.js +1 -1
- package/dist/features/auth/schemas/firestore.d.ts +0 -1
- package/dist/features/blog/schemas/firestore.d.ts +2 -2
- package/dist/features/catalogue/components/CatalogueItemEditorView.js +14 -3
- package/dist/features/categories/api/[id]/description-template/route.d.ts +45 -0
- package/dist/features/categories/api/[id]/description-template/route.js +252 -0
- package/dist/features/categories/api/route.js +156 -11
- package/dist/features/categories/components/CategoryDetailPageView.js +43 -10
- package/dist/features/categories/components/CategoryTemplatePicker.d.ts +65 -0
- package/dist/features/categories/components/CategoryTemplatePicker.js +170 -0
- package/dist/features/categories/constants/listing-categories.d.ts +17 -0
- package/dist/features/categories/constants/listing-categories.js +53 -0
- package/dist/features/categories/hooks/useCategoryTree.js +28 -2
- package/dist/features/categories/repository/categories.repository.d.ts +62 -11
- package/dist/features/categories/repository/categories.repository.js +178 -35
- package/dist/features/categories/repository/price-index.repository.d.ts +115 -0
- package/dist/features/categories/repository/price-index.repository.js +85 -0
- package/dist/features/categories/schemas/category-content.d.ts +206 -0
- package/dist/features/categories/schemas/category-content.js +28 -0
- package/dist/features/categories/schemas/firestore.d.ts +74 -5
- package/dist/features/categories/schemas/firestore.js +74 -3
- package/dist/features/categories/schemas/index.d.ts +1047 -0
- package/dist/features/categories/schemas/index.js +96 -0
- package/dist/features/categories/server.d.ts +1 -0
- package/dist/features/categories/server.js +7 -0
- package/dist/features/categories/types/index.d.ts +9 -0
- package/dist/features/events/schemas/firestore.d.ts +2 -2
- package/dist/features/faq/schemas/firestore.d.ts +2 -1
- package/dist/features/faq/schemas/firestore.js +20 -8
- package/dist/features/orders/schemas/firestore.d.ts +70 -0
- package/dist/features/orders/types/index.d.ts +55 -0
- package/dist/features/products/columns/index.d.ts +0 -1
- package/dist/features/products/columns/index.js +5 -1
- package/dist/features/products/components/ProductFilters.d.ts +0 -9
- package/dist/features/products/components/ProductForm.d.ts +27 -1
- package/dist/features/products/components/ProductForm.js +26 -12
- package/dist/features/products/constants/sieve.d.ts +0 -6
- package/dist/features/products/constants/sieve.js +35 -2
- package/dist/features/products/repository/products.repository.d.ts +115 -1
- package/dist/features/products/repository/products.repository.js +491 -39
- package/dist/features/products/schemas/firestore.d.ts +65 -5
- package/dist/features/products/schemas/firestore.js +9 -0
- package/dist/features/products/schemas/product-features.d.ts +140 -2
- package/dist/features/products/schemas/product-features.js +62 -1
- package/dist/features/promotions/repository/coupons.repository.js +6 -1
- package/dist/features/promotions/schemas/firestore.d.ts +44 -1
- package/dist/features/promotions/schemas/firestore.js +76 -2
- package/dist/features/seller/actions/seller-actions.js +17 -2
- package/dist/features/seller/components/PrintCenterView.d.ts +3 -2
- package/dist/features/seller/components/PrintCenterView.js +3 -2
- package/dist/features/seller/components/SellerAnalyticsAlertsView.js +21 -11
- package/dist/features/seller/components/SellerOrdersView.js +6 -1
- package/dist/features/seller/components/SellerPayoutSettingsView.js +19 -3
- package/dist/features/seller/components/SellerProductShell.d.ts +50 -2
- package/dist/features/seller/components/SellerProductShell.js +101 -9
- package/dist/features/seller/components/SellerProductsFilterDrawer.d.ts +13 -1
- package/dist/features/seller/components/SellerProductsFilterDrawer.js +2 -2
- package/dist/features/seller/components/SellerProductsView.js +108 -3
- package/dist/features/seller/types/index.d.ts +17 -0
- package/dist/features/seller/utils/product-draft-mapping.d.ts +13 -0
- package/dist/features/seller/utils/product-draft-mapping.js +17 -0
- package/dist/features/store-extensions/constants/alert-metrics.d.ts +86 -0
- package/dist/features/store-extensions/constants/alert-metrics.js +142 -0
- package/dist/features/store-extensions/schemas/firestore.d.ts +19 -1
- package/dist/features/stores/schemas/firestore.d.ts +31 -2
- package/dist/features/tax-codes/repository/tax-codes.repository.d.ts +55 -0
- package/dist/features/tax-codes/repository/tax-codes.repository.js +131 -0
- package/dist/features/tax-codes/schemas/firestore.d.ts +125 -0
- package/dist/features/tax-codes/schemas/firestore.js +82 -0
- package/dist/features/tax-codes/schemas/tax-code-form.d.ts +63 -0
- package/dist/features/tax-codes/schemas/tax-code-form.js +152 -0
- package/dist/features/tax-codes/schemas/tax-codes.validators.d.ts +107 -0
- package/dist/features/tax-codes/schemas/tax-codes.validators.js +85 -0
- package/dist/features/tester/actions/index.d.ts +0 -1
- package/dist/features/tester/actions/index.js +0 -1
- package/dist/features/tester/components/AdminTesterFeedbackIssuesView.js +1 -1
- package/dist/features/tester/components/AdminTesterFeedbackListView.js +1 -1
- package/dist/features/tester/components/index.d.ts +0 -4
- package/dist/features/tester/components/index.js +0 -2
- package/dist/features/tester/repository/tester-checklist-item.repository.d.ts +1 -20
- package/dist/features/tester/repository/tester-checklist-item.repository.js +0 -52
- package/dist/features/tester/schemas/firestore.d.ts +0 -6
- package/dist/features/tester/seed-data/authored/admin__admin-detail-round-trips.js +3 -3
- package/dist/features/tester/seed-data/authored/admin__site-system.js +4 -5
- package/dist/features/tester/seed-data/authored/content-discovery__category-counts-and-rollup.js +2 -2
- package/dist/features/tester/seed-data/authored/index.js +0 -2
- package/dist/features/tester/seed-data/authored/page-wiring__reachability.js +0 -19
- package/dist/features/tester/seed-data/tester-checklist-seed-data.js +8 -27
- package/dist/index.d.ts +21 -7
- package/dist/index.js +53 -7
- package/dist/next/routing/route-map.d.ts +2 -4
- package/dist/next/routing/route-map.js +1 -2
- package/dist/repositories/index.d.ts +1 -0
- package/dist/repositories/index.js +1 -0
- package/dist/schemas/registry.d.ts +506 -0
- package/dist/seed/_helpers/brand-rows.d.ts +35 -0
- package/dist/seed/_helpers/brand-rows.js +576 -0
- package/dist/seed/_helpers/category-content.d.ts +39 -0
- package/dist/seed/_helpers/category-content.js +634 -0
- package/dist/seed/_helpers/category-forest.d.ts +2 -0
- package/dist/seed/_helpers/category-forest.js +585 -243
- package/dist/seed/_helpers/category-models.d.ts +5 -0
- package/dist/seed/_helpers/category-models.js +1364 -0
- package/dist/seed/_helpers/category-tree.js +17 -1
- package/dist/seed/_helpers/character-features.d.ts +10 -0
- package/dist/seed/_helpers/character-features.js +118 -0
- package/dist/seed/_helpers/search-txt-wrappers.d.ts +10 -0
- package/dist/seed/_helpers/search-txt-wrappers.js +12 -1
- package/dist/seed/categories-seed-data.js +147 -169
- package/dist/seed/index.d.ts +1 -0
- package/dist/seed/index.js +1 -0
- package/dist/seed/manifest.d.ts +1 -1
- package/dist/seed/manifest.js +5 -0
- package/dist/seed/product-features-seed-data.d.ts +1 -8
- package/dist/seed/product-features-seed-data.js +358 -154
- package/dist/seed/products-art-seed-data.js +16 -4
- package/dist/seed/products-auctions-seed-data.js +6 -6
- package/dist/seed/products-classifieds-seed-data.js +17 -7
- package/dist/seed/products-digital-codes-seed-data.js +12 -12
- package/dist/seed/products-preorders-seed-data.js +8 -8
- package/dist/seed/products-prize-draws-seed-data.js +25 -8
- package/dist/seed/products-standard-seed-data.js +10 -60
- package/dist/seed/products-stickers-seed-data.js +6 -6
- package/dist/seed/site-settings-seed-data.js +24 -0
- package/dist/seed/store-extensions-seed-data.js +10 -4
- package/dist/seed/tax-codes-seed-data.d.ts +34 -0
- package/dist/seed/tax-codes-seed-data.js +100 -0
- package/dist/seed/test-data-ttl.d.ts +29 -0
- package/dist/seed/test-data-ttl.js +31 -0
- package/dist/server-entry.d.ts +1 -1
- package/dist/server-entry.js +6 -1
- package/dist/server.d.ts +2 -1
- package/dist/server.js +5 -2
- package/dist/styles.css +40 -0
- package/dist/ui/components/PaginatedSelect.d.ts +14 -0
- package/dist/ui/components/PaginatedSelect.js +0 -0
- package/dist/ui/components/PaginatedSelect.style.css +40 -0
- package/dist/utils/physical-location.d.ts +23 -0
- package/dist/utils/physical-location.js +52 -0
- package/dist/utils/search-txt-builders.d.ts +20 -0
- package/dist/utils/search-txt-builders.js +36 -0
- package/package.json +1 -1
- package/scripts/seed-cli.mjs +4 -1
- package/dist/_internal/server/jobs/handlers/testerSandboxCleanup.d.ts +0 -2
- package/dist/_internal/server/jobs/handlers/testerSandboxCleanup.js +0 -2
- package/dist/features/tester/actions/leaderboard-actions.d.ts +0 -3
- package/dist/features/tester/actions/leaderboard-actions.js +0 -5
- package/dist/features/tester/components/BugHunterLeaderboardView.d.ts +0 -23
- package/dist/features/tester/components/BugHunterLeaderboardView.js +0 -18
- package/dist/features/tester/components/TesterHubView.d.ts +0 -5
- package/dist/features/tester/components/TesterHubView.js +0 -110
- package/dist/features/tester/seed-data/authored/public-pages__bug-hunters.d.ts +0 -2
- package/dist/features/tester/seed-data/authored/public-pages__bug-hunters.js +0 -78
- package/dist/features/tester/seed-data/tester-ttl.d.ts +0 -4
- package/dist/features/tester/seed-data/tester-ttl.js +0 -6
|
@@ -36,7 +36,23 @@
|
|
|
36
36
|
*/
|
|
37
37
|
export function buildCategoryTree(roots, opts) {
|
|
38
38
|
const rows = [];
|
|
39
|
-
|
|
39
|
+
/*
|
|
40
|
+
* 🛑 1-BASED, and that is not cosmetic — it must match `positionsReconcile`,
|
|
41
|
+
* which starts its own DFS at `let counter = 1`.
|
|
42
|
+
*
|
|
43
|
+
* This counter started at 0 until 2026-10-10. The nightly reconcile then
|
|
44
|
+
* found every row off by one (`current.position !== position`) and rewrote
|
|
45
|
+
* the entire collection on the first run after any reseed — 58 writes then,
|
|
46
|
+
* ~330 once the catalogue grows, each re-entering `onCategoryWrite` as a
|
|
47
|
+
* no-op invocation. Measured in production before the change: seed emits
|
|
48
|
+
* 0..57, live data reads 1..58, i.e. the reconcile had already corrected
|
|
49
|
+
* all of it.
|
|
50
|
+
*
|
|
51
|
+
* It also makes position 0 UNAMBIGUOUS, which `handleCategoryWrite` now
|
|
52
|
+
* relies on: a document arriving with `position >= 1` was numbered by this
|
|
53
|
+
* builder, and one with 0/absent was not.
|
|
54
|
+
*/
|
|
55
|
+
let position = 1;
|
|
40
56
|
function visit(node, rootId,
|
|
41
57
|
/** Ancestors nearest-last, matching how `parentIds` is read elsewhere. */
|
|
42
58
|
ancestors, tier, parentPath, order) {
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
/** A blader, and how many canonical beys the corpus attributes to them. */
|
|
2
|
+
export interface CharacterSeed {
|
|
3
|
+
/** Without the `feature-` prefix — the factory prepends it. */
|
|
4
|
+
slug: string;
|
|
5
|
+
name: string;
|
|
6
|
+
/** Canonical beys owned. Drives display order; not stored on the row. */
|
|
7
|
+
beyCount: number;
|
|
8
|
+
}
|
|
9
|
+
export declare const CHARACTER_SEEDS: CharacterSeed[];
|
|
10
|
+
export declare const CHARACTER_SEED_COUNT = 93;
|
|
@@ -0,0 +1,118 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* GENERATED by scripts/gen-category-models.mjs — do not edit by hand.
|
|
3
|
+
*
|
|
4
|
+
* WHY: the clickable owner linkage. `Owner (Anime)` is 100% populated in the
|
|
5
|
+
* corpus but it is a SENTENCE — "Tyson Granger (Tyson's 9th and final
|
|
6
|
+
* bey)" — so it renders as prose and filters nothing. These are the
|
|
7
|
+
* normalised names as `character`-group features, which makes
|
|
8
|
+
* "show me every Tyson Granger bey" one click from any listing page.
|
|
9
|
+
*
|
|
10
|
+
* 🛑 A FEATURE and never a category node. A blader crosses every generation —
|
|
11
|
+
* Tyson owns nine beys spanning Spin Gear, Magnacore, Engine Gear and HMS —
|
|
12
|
+
* so a tree node would duplicate all nine under a second parent and break
|
|
13
|
+
* the one-chain-per-product invariant. As a feature it COMPOSES with the
|
|
14
|
+
* generation tree instead of competing with it: browse Beyblade Original,
|
|
15
|
+
* then narrow to Tyson's.
|
|
16
|
+
*
|
|
17
|
+
* @tag domain:products
|
|
18
|
+
* @tag layer:seed
|
|
19
|
+
* @tag pattern:generated
|
|
20
|
+
* @tag access:server-only
|
|
21
|
+
* @tag sideEffects:none
|
|
22
|
+
*/
|
|
23
|
+
export const CHARACTER_SEEDS = [
|
|
24
|
+
{ slug: "blader-tyson-granger", name: "Tyson Granger", beyCount: 9 },
|
|
25
|
+
{ slug: "blader-kai-hiwatari", name: "Kai Hiwatari", beyCount: 7 },
|
|
26
|
+
{ slug: "blader-daichi-sumeragi", name: "Daichi Sumeragi", beyCount: 6 },
|
|
27
|
+
{ slug: "blader-max-tate", name: "Max Tate", beyCount: 6 },
|
|
28
|
+
{ slug: "blader-ray-kon", name: "Ray Kon", beyCount: 6 },
|
|
29
|
+
{ slug: "blader-shu-kurenai", name: "Shu Kurenai", beyCount: 6 },
|
|
30
|
+
{ slug: "blader-gingka-hagane", name: "Gingka Hagane", beyCount: 5 },
|
|
31
|
+
{ slug: "blader-lui-shirosagi", name: "Lui Shirosagi", beyCount: 5 },
|
|
32
|
+
{ slug: "blader-tala", name: "Tala", beyCount: 5 },
|
|
33
|
+
{ slug: "blader-valt-aoi", name: "Valt Aoi", beyCount: 5 },
|
|
34
|
+
{ slug: "blader-free-de-la-hoya", name: "Free De La Hoya", beyCount: 4 },
|
|
35
|
+
{ slug: "blader-aiger-akabane", name: "Aiger Akabane", beyCount: 3 },
|
|
36
|
+
{ slug: "blader-dante-koryu", name: "Dante Koryu", beyCount: 3 },
|
|
37
|
+
{ slug: "blader-jaxon-cross", name: "Jaxon Cross", beyCount: 3 },
|
|
38
|
+
{ slug: "blader-kenta-yumiya", name: "Kenta Yumiya", beyCount: 3 },
|
|
39
|
+
{ slug: "blader-kyoya-tategami", name: "Kyoya Tategami", beyCount: 3 },
|
|
40
|
+
{ slug: "blader-multi-nana-iro", name: "Multi Nana-iro", beyCount: 3 },
|
|
41
|
+
{ slug: "blader-ryuga", name: "Ryuga", beyCount: 3 },
|
|
42
|
+
{ slug: "blader-benkei-hanawa", name: "Benkei Hanawa", beyCount: 2 },
|
|
43
|
+
{ slug: "blader-daigo-kurogami", name: "Daigo Kurogami", beyCount: 2 },
|
|
44
|
+
{ slug: "blader-damian-hart", name: "Damian Hart", beyCount: 2 },
|
|
45
|
+
{ slug: "blader-dashan-wang", name: "Dashan Wang", beyCount: 2 },
|
|
46
|
+
{ slug: "blader-delta-zakuro", name: "Delta Zakuro", beyCount: 2 },
|
|
47
|
+
{ slug: "blader-dunga", name: "Dunga", beyCount: 2 },
|
|
48
|
+
{ slug: "blader-eddy", name: "Eddy", beyCount: 2 },
|
|
49
|
+
{ slug: "blader-fumiya-kindo", name: "Fumiya Kindo", beyCount: 2 },
|
|
50
|
+
{ slug: "blader-hikaru-hizashi", name: "Hikaru Hizashi", beyCount: 2 },
|
|
51
|
+
{ slug: "blader-hyde", name: "Hyde", beyCount: 2 },
|
|
52
|
+
{ slug: "blader-jin-of-the-gale", name: "Jin of the Gale", beyCount: 2 },
|
|
53
|
+
{ slug: "blader-kira-hayama", name: "Kira Hayama", beyCount: 2 },
|
|
54
|
+
{ slug: "blader-lee", name: "Lee", beyCount: 2 },
|
|
55
|
+
{ slug: "blader-masamune-kadoya", name: "Masamune Kadoya", beyCount: 2 },
|
|
56
|
+
{ slug: "blader-michael-summers", name: "Michael Summers", beyCount: 2 },
|
|
57
|
+
{ slug: "blader-miguel", name: "Miguel", beyCount: 2 },
|
|
58
|
+
{ slug: "blader-ozuma", name: "Ozuma", beyCount: 2 },
|
|
59
|
+
{ slug: "blader-robin-kazami", name: "Robin Kazami", beyCount: 2 },
|
|
60
|
+
{ slug: "blader-tsubasa-otori", name: "Tsubasa Otori", beyCount: 2 },
|
|
61
|
+
{ slug: "blader-wakiya-murasaki", name: "Wakiya Murasaki", beyCount: 2 },
|
|
62
|
+
{ slug: "blader-xander-shakadera", name: "Xander Shakadera", beyCount: 2 },
|
|
63
|
+
{ slug: "blader-yu-tendo", name: "Yu Tendo", beyCount: 2 },
|
|
64
|
+
{ slug: "blader-aguma", name: "Aguma", beyCount: 1 },
|
|
65
|
+
{ slug: "blader-akuya-onizaki", name: "Akuya Onizaki", beyCount: 1 },
|
|
66
|
+
{ slug: "blader-anime", name: "Anime", beyCount: 1 },
|
|
67
|
+
{ slug: "blader-anime-secondary", name: "Anime-secondary", beyCount: 1 },
|
|
68
|
+
{ slug: "blader-blader-dj", name: "Blader DJ", beyCount: 1 },
|
|
69
|
+
{ slug: "blader-brooklyn", name: "Brooklyn", beyCount: 1 },
|
|
70
|
+
{ slug: "blader-chris", name: "Chris", beyCount: 1 },
|
|
71
|
+
{ slug: "blader-claude", name: "Claude", beyCount: 1 },
|
|
72
|
+
{ slug: "blader-clio-delon", name: "Clio Delon", beyCount: 1 },
|
|
73
|
+
{ slug: "blader-crusher", name: "Crusher", beyCount: 1 },
|
|
74
|
+
{ slug: "blader-cycnus", name: "Cycnus", beyCount: 1 },
|
|
75
|
+
{ slug: "blader-dan-sodo-and-reiki-sodo", name: "Dan Sodo and Reiki Sodo", beyCount: 1 },
|
|
76
|
+
{ slug: "blader-doji", name: "Doji", beyCount: 1 },
|
|
77
|
+
{ slug: "blader-faust", name: "Faust", beyCount: 1 },
|
|
78
|
+
{ slug: "blader-garland-siebald", name: "Garland Siebald", beyCount: 1 },
|
|
79
|
+
{ slug: "blader-gary", name: "Gary", beyCount: 1 },
|
|
80
|
+
{ slug: "blader-ginro", name: "Ginro", beyCount: 1 },
|
|
81
|
+
{ slug: "blader-hikaru-hasama", name: "Hikaru Hasama", beyCount: 1 },
|
|
82
|
+
{ slug: "blader-hokuto", name: "Hokuto", beyCount: 1 },
|
|
83
|
+
{ slug: "blader-hyoma", name: "Hyoma", beyCount: 1 },
|
|
84
|
+
{ slug: "blader-hyuga-hizashi", name: "Hyuga Hizashi", beyCount: 1 },
|
|
85
|
+
{ slug: "blader-ian-papov", name: "Ian Papov", beyCount: 1 },
|
|
86
|
+
{ slug: "blader-johnny-mcgregor", name: "Johnny McGregor", beyCount: 1 },
|
|
87
|
+
{ slug: "blader-joseph-burton", name: "Joseph Burton", beyCount: 1 },
|
|
88
|
+
{ slug: "blader-julia-fernandez", name: "Julia Fernandez", beyCount: 1 },
|
|
89
|
+
{ slug: "blader-julian-konzern", name: "Julian Konzern", beyCount: 1 },
|
|
90
|
+
{ slug: "blader-karla-konjiki", name: "Karla Konjiki", beyCount: 1 },
|
|
91
|
+
{ slug: "blader-ken-midori", name: "Ken Midori", beyCount: 1 },
|
|
92
|
+
{ slug: "blader-kenny", name: "Kenny", beyCount: 1 },
|
|
93
|
+
{ slug: "blader-kevin", name: "Kevin", beyCount: 1 },
|
|
94
|
+
{ slug: "blader-kite-unabara", name: "Kite Unabara", beyCount: 1 },
|
|
95
|
+
{ slug: "blader-lain-valhalla", name: "Lain Valhalla", beyCount: 1 },
|
|
96
|
+
{ slug: "blader-meiko-myoden", name: "Meiko Myoden", beyCount: 1 },
|
|
97
|
+
{ slug: "blader-nile", name: "Nile", beyCount: 1 },
|
|
98
|
+
{ slug: "blader-nine-kurosu", name: "Nine Kurosu", beyCount: 1 },
|
|
99
|
+
{ slug: "blader-omega-shiroboshi", name: "Omega Shiroboshi", beyCount: 1 },
|
|
100
|
+
{ slug: "blader-phi", name: "Phi", beyCount: 1 },
|
|
101
|
+
{ slug: "blader-rago", name: "Rago", beyCount: 1 },
|
|
102
|
+
{ slug: "blader-reiji-mizuchi", name: "Reiji Mizuchi", beyCount: 1 },
|
|
103
|
+
{ slug: "blader-reiyu-kuwabara", name: "Reiyu Kuwabara", beyCount: 1 },
|
|
104
|
+
{ slug: "blader-rex-jura", name: "Rex Jura", beyCount: 1 },
|
|
105
|
+
{ slug: "blader-rick-anderson", name: "Rick Anderson", beyCount: 1 },
|
|
106
|
+
{ slug: "blader-robert-jurgens", name: "Robert Jurgens", beyCount: 1 },
|
|
107
|
+
{ slug: "blader-sakyo-kurayami", name: "Sakyo Kurayami", beyCount: 1 },
|
|
108
|
+
{ slug: "blader-silas-karlisle", name: "Silas Karlisle", beyCount: 1 },
|
|
109
|
+
{ slug: "blader-spencer", name: "Spencer", beyCount: 1 },
|
|
110
|
+
{ slug: "blader-teru-saotome", name: "Teru Saotome", beyCount: 1 },
|
|
111
|
+
{ slug: "blader-tobio-oike", name: "Tobio Oike", beyCount: 1 },
|
|
112
|
+
{ slug: "blader-toby", name: "Toby", beyCount: 1 },
|
|
113
|
+
{ slug: "blader-toguro-okunaga", name: "Toguro Okunaga", beyCount: 1 },
|
|
114
|
+
{ slug: "blader-yuki-mizusawa", name: "Yuki Mizusawa", beyCount: 1 },
|
|
115
|
+
{ slug: "blader-yuni-naniwa", name: "Yuni Naniwa", beyCount: 1 },
|
|
116
|
+
{ slug: "blader-zyro-kurogane", name: "Zyro Kurogane", beyCount: 1 },
|
|
117
|
+
];
|
|
118
|
+
export const CHARACTER_SEED_COUNT = 93;
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import type { CategoryDocument } from "../../features/categories/schemas/firestore";
|
|
1
2
|
import type { StoreDocument } from "../../features/stores/schemas/firestore";
|
|
2
3
|
import type { ScammerDocument } from "../../features/scams/schemas/firestore";
|
|
3
4
|
import type { EventDocument } from "../../features/events/schemas/firestore";
|
|
@@ -6,6 +7,15 @@ import type { ReviewDocument } from "../../features/reviews/schemas/firestore";
|
|
|
6
7
|
import type { OrderDocument } from "../../features/orders/schemas/firestore";
|
|
7
8
|
import type { CouponDocument } from "../../features/promotions/schemas/firestore";
|
|
8
9
|
import type { OfferDocument } from "../../features/seller/schemas/firestore";
|
|
10
|
+
/**
|
|
11
|
+
* 🛑 Apply AFTER `buildCategoryTree`, never before.
|
|
12
|
+
*
|
|
13
|
+
* `buildCategorySearchTxt` indexes `path` and `ancestors[].name`, and both are
|
|
14
|
+
* DERIVED by the tree builder — a row wrapped before derivation gets tokens
|
|
15
|
+
* from `name`/`slug`/`description` only, and silently loses the lineage search
|
|
16
|
+
* that is the whole reason ancestors are indexed.
|
|
17
|
+
*/
|
|
18
|
+
export declare function withCategorySearchTxt<T extends Partial<CategoryDocument>>(p: T): T;
|
|
9
19
|
export declare function withStoreSearchTxt<T extends Partial<StoreDocument>>(p: T): T;
|
|
10
20
|
export declare function withEventSearchTxt<T extends Partial<EventDocument>>(p: T): T;
|
|
11
21
|
export declare function withBlogSearchTxt<T extends Partial<BlogPostDocument>>(p: T): T;
|
|
@@ -1,4 +1,15 @@
|
|
|
1
|
-
import { buildStoreSearchTxt, buildEventSearchTxt, buildBlogSearchTxt, buildReviewSearchTxt, buildOrderSearchTxt, buildCouponSearchTxt, buildOfferSearchTxt, buildScammerSearchTxt, } from "../../utils/search-txt-builders";
|
|
1
|
+
import { buildCategorySearchTxt, buildStoreSearchTxt, buildEventSearchTxt, buildBlogSearchTxt, buildReviewSearchTxt, buildOrderSearchTxt, buildCouponSearchTxt, buildOfferSearchTxt, buildScammerSearchTxt, } from "../../utils/search-txt-builders";
|
|
2
|
+
/**
|
|
3
|
+
* 🛑 Apply AFTER `buildCategoryTree`, never before.
|
|
4
|
+
*
|
|
5
|
+
* `buildCategorySearchTxt` indexes `path` and `ancestors[].name`, and both are
|
|
6
|
+
* DERIVED by the tree builder — a row wrapped before derivation gets tokens
|
|
7
|
+
* from `name`/`slug`/`description` only, and silently loses the lineage search
|
|
8
|
+
* that is the whole reason ancestors are indexed.
|
|
9
|
+
*/
|
|
10
|
+
export function withCategorySearchTxt(p) {
|
|
11
|
+
return { ...p, searchTxt: buildCategorySearchTxt(p) };
|
|
12
|
+
}
|
|
2
13
|
export function withStoreSearchTxt(p) {
|
|
3
14
|
return { ...p, searchTxt: buildStoreSearchTxt(p) };
|
|
4
15
|
}
|
|
@@ -1,17 +1,34 @@
|
|
|
1
1
|
/*
|
|
2
|
-
* WHY:
|
|
3
|
-
*
|
|
4
|
-
*
|
|
5
|
-
*
|
|
6
|
-
*
|
|
7
|
-
*
|
|
8
|
-
*
|
|
2
|
+
* WHY: The real catalogue taxonomy. 🛑 This file is NO LONGER Beyblade-only —
|
|
3
|
+
* its header said "narrowed to Beyblade so the demo has a coherent
|
|
4
|
+
* dataset instead of a sprawling multi-franchise catalog" and B5 reversed
|
|
5
|
+
* that deliberately, on evidence: a 15-site crawl of ~25,900 live Indian
|
|
6
|
+
* listings showed trading cards, collectible figures and Hot Wheels are
|
|
7
|
+
* each a real vertical with its own measured price bands, and three of
|
|
8
|
+
* the five largest competitors sell more than one of them.
|
|
9
9
|
*
|
|
10
|
-
*
|
|
10
|
+
* The old framing is not contradicted here, it is superseded — coherence
|
|
11
|
+
* now comes from the TREE being one axis deep per vertical rather than
|
|
12
|
+
* from the catalogue being one franchise wide.
|
|
13
|
+
*
|
|
14
|
+
* WHAT: Exports categoriesSeedData — a 5-level, SIX-root forest (Spinning Tops,
|
|
15
|
+
* Trading Cards, Collectible Figures, Hot Wheels, Model Kits, Living
|
|
16
|
+
* Collectibles — see _helpers/category-forest.ts), 28 brand rows
|
|
17
|
+
* (categoryType:"brand", _helpers/brand-rows.ts), two sublisting rows and
|
|
18
|
+
* five pricing bundles (categoryType:"bundle").
|
|
19
|
+
*
|
|
20
|
+
* The tree was 2 levels under a single root until 2026-08-24 and 47 nodes
|
|
21
|
+
* across 2 roots until B5. The first state made
|
|
11
22
|
* `categoryType:"sublisting"` — documented as "tier-4 leaf groups under a
|
|
12
23
|
* parent category" — structurally unreachable, and left the live-item
|
|
13
24
|
* products with no category at all.
|
|
14
25
|
*
|
|
26
|
+
* The tier-4 layer is 216 NAMED MODEL leaves (Lost Longinus, Storm
|
|
27
|
+
* Pegasus, Dran Sword …), GENERATED from the canonical corpus in
|
|
28
|
+
* _helpers/category-models.ts rather than typed by hand — a slug is the
|
|
29
|
+
* document id, the public URL and a searchTxt source, so it is the one
|
|
30
|
+
* field that cannot be quietly corrected later.
|
|
31
|
+
*
|
|
15
32
|
* Products tag their FULL ancestor chain (leaf -> … -> root) so a single
|
|
16
33
|
* array-contains at any level returns the whole subtree. That is what lets
|
|
17
34
|
* a category page match on its own id alone; expanding descendants into an
|
|
@@ -36,7 +53,12 @@
|
|
|
36
53
|
import { CATEGORY_FIELDS } from "../constants/field-names";
|
|
37
54
|
import { seedPhoto } from "./_helpers/media";
|
|
38
55
|
import { buildCategoryTree } from "./_helpers/category-tree";
|
|
56
|
+
import { withCategorySearchTxt } from "./_helpers/search-txt-wrappers";
|
|
39
57
|
import { CATEGORY_FOREST } from "./_helpers/category-forest";
|
|
58
|
+
import { BRAND_ROW_COUNT, buildBrandRows } from "./_helpers/brand-rows";
|
|
59
|
+
import { MODEL_LEAF_COUNT } from "./_helpers/category-models";
|
|
60
|
+
import { MARKET_BAND_LEAF_IDS } from "./_helpers/category-content";
|
|
61
|
+
import { productsStandardSeedData } from "./products-standard-seed-data";
|
|
40
62
|
const NOW = new Date();
|
|
41
63
|
const daysAgo = (n) => new Date(NOW.getTime() - n * 86400000);
|
|
42
64
|
const emptyMetrics = {
|
|
@@ -50,9 +72,14 @@ const emptyMetrics = {
|
|
|
50
72
|
lastUpdated: daysAgo(1),
|
|
51
73
|
};
|
|
52
74
|
// ──────────────────────────────────────────────────────────────────────────────
|
|
53
|
-
// Categories —
|
|
54
|
-
// The shape lives in _helpers/category-forest.ts
|
|
55
|
-
// _helpers/category-
|
|
75
|
+
// Categories — 381 rows across 5 tiers and 6 roots, every structural field
|
|
76
|
+
// derived. The shape lives in _helpers/category-forest.ts, the named-model
|
|
77
|
+
// tier-4 leaves in the GENERATED _helpers/category-models.ts, and the
|
|
78
|
+
// derivation in _helpers/category-tree.ts.
|
|
79
|
+
//
|
|
80
|
+
// 🛑 Never quote this count without running `buildCategoryTree` against the
|
|
81
|
+
// forest — it was 47 before B5 and the comment here said so for months after
|
|
82
|
+
// the tree changed underneath it.
|
|
56
83
|
// ──────────────────────────────────────────────────────────────────────────────
|
|
57
84
|
const rawCategories = buildCategoryTree(CATEGORY_FOREST, {
|
|
58
85
|
createdBy: "user-admin-letitrip",
|
|
@@ -153,157 +180,61 @@ const sublistingRows = [
|
|
|
153
180
|
},
|
|
154
181
|
];
|
|
155
182
|
// ──────────────────────────────────────────────────────────────────────────────
|
|
156
|
-
// Brands (categoryType:"brand").
|
|
183
|
+
// Brands (categoryType:"brand") — 4 rows -> 28, built by a factory.
|
|
157
184
|
//
|
|
158
|
-
//
|
|
159
|
-
//
|
|
160
|
-
//
|
|
161
|
-
//
|
|
162
|
-
// Keep `brand` and `brandSlug` in lockstep on the product side.
|
|
185
|
+
// The shape, the three live traps (display-name matching, icon-beats-cover,
|
|
186
|
+
// and why a brand with no products is worse than no brand) and the logo
|
|
187
|
+
// decision all live in _helpers/brand-rows.ts. Four inline rows at ~35 lines
|
|
188
|
+
// each was already the wrong shape for 28.
|
|
163
189
|
// ──────────────────────────────────────────────────────────────────────────────
|
|
164
|
-
const brandRows =
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
categoryType: CATEGORY_FIELDS.CATEGORY_TYPE_VALUES.BRAND,
|
|
170
|
-
description: "Japanese toy company behind Beyblade, Tomica, Transformers (Japan), and Duel Masters. Known for premium quality and Japan-exclusive releases.",
|
|
171
|
-
brandWebsite: "https://www.takaratomy.co.jp",
|
|
172
|
-
brandCountry: "Japan",
|
|
173
|
-
brandFounded: 2006,
|
|
174
|
-
highlights: [
|
|
175
|
-
"The Japanese toy company that manufactures every Beyblade generation",
|
|
176
|
-
"Also behind Tomica, Transformers (Japan region), and Duel Masters",
|
|
177
|
-
"Known for Japan-exclusive releases and premium tooling quality",
|
|
178
|
-
],
|
|
179
|
-
faqs: [
|
|
180
|
-
{ question: "Is Takara-Tomy the same company as Hasbro's Beyblade line?", answer: "No — Takara-Tomy manufactures and sells Beyblade in Japan and most of Asia; Hasbro licenses and distributes a separate (sometimes different) product line internationally, primarily in North America." },
|
|
181
|
-
{ question: "Are Takara-Tomy imports compatible with Hasbro Beyblade parts?", answer: "Within the same generation (e.g. Burst-to-Burst), yes, in most cases — cross-generation compatibility is not guaranteed." },
|
|
182
|
-
],
|
|
183
|
-
rootId: "brand-takara-tomy",
|
|
184
|
-
parentIds: [],
|
|
185
|
-
tier: 0,
|
|
186
|
-
path: "brand-takara-tomy",
|
|
187
|
-
isLeaf: true,
|
|
188
|
-
order: 1,
|
|
189
|
-
display: { coverImage: seedPhoto("brand-logo-takara-tomy-20260101", 800, 800), showInMenu: false, showInFooter: true },
|
|
190
|
-
isFeatured: true,
|
|
191
|
-
isBrand: true,
|
|
192
|
-
isActive: true,
|
|
193
|
-
isSearchable: true,
|
|
194
|
-
createdBy: "user-admin-letitrip",
|
|
195
|
-
createdAt: daysAgo(300),
|
|
196
|
-
updatedAt: daysAgo(30),
|
|
197
|
-
seo: { title: "Takara-Tomy | LetItRip", description: "Shop Takara-Tomy collectibles — Beyblade, Tomica, Transformers.", keywords: ["takara tomy", "takara tomy beyblade", "tomica takara"] },
|
|
198
|
-
},
|
|
199
|
-
{
|
|
200
|
-
id: "brand-beyblade",
|
|
201
|
-
slug: "brand-beyblade",
|
|
202
|
-
name: "Beyblade",
|
|
203
|
-
categoryType: CATEGORY_FIELDS.CATEGORY_TYPE_VALUES.BRAND,
|
|
204
|
-
description: "Spinning top battle franchise by Takara-Tomy (Japan) and Hasbro (international). Covers the Original series, Metal Fight, Burst, and the latest Beyblade X generation.",
|
|
205
|
-
brandWebsite: "https://beyblade.takaratomy.co.jp",
|
|
206
|
-
brandCountry: "Japan",
|
|
207
|
-
brandFounded: 1999,
|
|
208
|
-
highlights: [
|
|
209
|
-
"The franchise itself — spanning four generations since 1999",
|
|
210
|
-
"One of the best-selling spinning-top toy lines in the world",
|
|
211
|
-
"A competitive tournament scene alongside casual collecting",
|
|
212
|
-
],
|
|
213
|
-
faqs: [
|
|
214
|
-
{ question: "What age range is Beyblade designed for?", answer: "Most sets are labeled for ages 8+ due to small parts and launcher spring mechanisms — always check the age rating on the listing before buying for a young child." },
|
|
215
|
-
{ question: "Where can I find official tournament rules?", answer: "Takara-Tomy and Hasbro both publish official battle rules for their respective regions — check the manufacturer's site linked from this page's \"Website\" field for the current rulebook." },
|
|
216
|
-
],
|
|
217
|
-
rootId: "brand-beyblade",
|
|
218
|
-
parentIds: [],
|
|
219
|
-
tier: 0,
|
|
220
|
-
path: "brand-beyblade",
|
|
221
|
-
isLeaf: true,
|
|
222
|
-
order: 2,
|
|
223
|
-
display: { coverImage: seedPhoto("brand-logo-beyblade-20260101", 800, 800), showInMenu: false, showInFooter: true },
|
|
224
|
-
isFeatured: true,
|
|
225
|
-
isBrand: true,
|
|
226
|
-
isActive: true,
|
|
227
|
-
isSearchable: true,
|
|
228
|
-
createdBy: "user-admin-letitrip",
|
|
229
|
-
createdAt: daysAgo(300),
|
|
230
|
-
updatedAt: daysAgo(30),
|
|
231
|
-
seo: { title: "Beyblade | LetItRip", description: "Shop Beyblade — Original, X, Burst, Metal Fight tops and stadiums.", keywords: ["beyblade", "beyblade x", "beyblade burst", "spinning top battle"] },
|
|
232
|
-
},
|
|
233
|
-
{
|
|
234
|
-
id: "brand-hasbro",
|
|
235
|
-
slug: "brand-hasbro",
|
|
236
|
-
name: "Hasbro",
|
|
237
|
-
categoryType: CATEGORY_FIELDS.CATEGORY_TYPE_VALUES.BRAND,
|
|
238
|
-
description: "The international Beyblade licensee — Hasbro distributes its own Beyblade product line outside Japan, with different packaging, part names and sometimes different mould tolerances from the Takara-Tomy originals.",
|
|
239
|
-
brandWebsite: "https://shop.hasbro.com",
|
|
240
|
-
brandCountry: "United States",
|
|
241
|
-
brandFounded: 1923,
|
|
242
|
-
highlights: [
|
|
243
|
-
"The Beyblade line most collectors outside Japan grew up with",
|
|
244
|
-
"Widely available and generally cheaper than Japanese imports",
|
|
245
|
-
"Same-generation parts are usually cross-compatible with Takara-Tomy",
|
|
246
|
-
],
|
|
247
|
-
faqs: [
|
|
248
|
-
{ question: "Is a Hasbro Beyblade worse than the Takara-Tomy version?", answer: "Not worse, but often different — Hasbro releases can use different plastics and slightly looser tolerances, which competitive players notice. For casual play they perform comparably." },
|
|
249
|
-
{ question: "Can I mix Hasbro and Takara-Tomy parts?", answer: "Within the same generation, almost always yes. Across generations, no — the launcher and locking systems differ." },
|
|
250
|
-
],
|
|
251
|
-
rootId: "brand-hasbro",
|
|
252
|
-
parentIds: [],
|
|
253
|
-
tier: 0,
|
|
254
|
-
path: "brand-hasbro",
|
|
255
|
-
isLeaf: true,
|
|
256
|
-
order: 3,
|
|
257
|
-
display: { coverImage: seedPhoto("brand-logo-hasbro-20260101", 800, 800), showInMenu: false, showInFooter: true },
|
|
258
|
-
isFeatured: false,
|
|
259
|
-
isBrand: true,
|
|
260
|
-
isActive: true,
|
|
261
|
-
isSearchable: true,
|
|
262
|
-
createdBy: "user-admin-letitrip",
|
|
263
|
-
createdAt: daysAgo(300),
|
|
264
|
-
updatedAt: daysAgo(30),
|
|
265
|
-
seo: { title: "Hasbro | LetItRip", description: "Shop Hasbro Beyblade — the international product line.", keywords: ["hasbro", "hasbro beyblade", "beyblade burst hasbro"] },
|
|
266
|
-
},
|
|
267
|
-
{
|
|
268
|
-
// The Living Collectibles root needs a brand of its own: live listings are
|
|
269
|
-
// sold by individual keepers and breeders, not by a manufacturer. Without
|
|
270
|
-
// one, the three live-item products carried a `brand: "Beyblade Arena"`
|
|
271
|
-
// string (a STORE name in a brand field) that matched no brand row, so they
|
|
272
|
-
// were invisible on every brand page.
|
|
273
|
-
id: "brand-independent-keepers",
|
|
274
|
-
slug: "brand-independent-keepers",
|
|
275
|
-
name: "Independent Keepers",
|
|
276
|
-
categoryType: CATEGORY_FIELDS.CATEGORY_TYPE_VALUES.BRAND,
|
|
277
|
-
description: "Live animals and plants come from individual keepers, breeders and growers rather than a manufacturer. This entry groups those listings so they are reachable from brand browsing like everything else.",
|
|
278
|
-
brandCountry: "India",
|
|
279
|
-
highlights: [
|
|
280
|
-
"Every seller is verified before a live listing can go public",
|
|
281
|
-
"Provenance, age and health information disclosed per listing",
|
|
282
|
-
],
|
|
283
|
-
faqs: [
|
|
284
|
-
{ question: "Why is there a \"brand\" for living things at all?", answer: "Purely so live listings behave like every other listing in browse and search. It identifies the class of seller, not a manufacturer." },
|
|
285
|
-
],
|
|
286
|
-
rootId: "brand-independent-keepers",
|
|
287
|
-
parentIds: [],
|
|
288
|
-
tier: 0,
|
|
289
|
-
path: "brand-independent-keepers",
|
|
290
|
-
isLeaf: true,
|
|
291
|
-
order: 4,
|
|
292
|
-
display: { coverImage: seedPhoto("brand-logo-independent-keepers-20260101", 800, 800), showInMenu: false, showInFooter: false },
|
|
293
|
-
isFeatured: false,
|
|
294
|
-
isBrand: true,
|
|
295
|
-
isActive: true,
|
|
296
|
-
isSearchable: true,
|
|
297
|
-
createdBy: "user-admin-letitrip",
|
|
298
|
-
createdAt: daysAgo(300),
|
|
299
|
-
updatedAt: daysAgo(30),
|
|
300
|
-
seo: { title: "Independent Keepers | LetItRip", description: "Live animals and plants from verified independent keepers and growers.", keywords: ["independent keepers", "breeders", "live plants"] },
|
|
301
|
-
},
|
|
302
|
-
];
|
|
190
|
+
const brandRows = buildBrandRows({
|
|
191
|
+
createdBy: "user-admin-letitrip",
|
|
192
|
+
createdAt: daysAgo(300),
|
|
193
|
+
updatedAt: daysAgo(30),
|
|
194
|
+
});
|
|
303
195
|
// P-17 — 5 bundle rows (categoryType:"bundle") grouping the Beyblade-minimal
|
|
304
196
|
// standard products by generation, plus one cross-generation starter pack.
|
|
305
197
|
// Every bundle references real seeded product ids (products-standard-seed-data.ts),
|
|
306
198
|
// all currently under store-beyblade-arena.
|
|
199
|
+
/*
|
|
200
|
+
* 🛑 `bundleCategorySlugs` is DERIVED, because it is a MIRROR and mirrors
|
|
201
|
+
* drift (Root Cause #42).
|
|
202
|
+
*
|
|
203
|
+
* The field is the union of the members' `categorySlugs`, and it is what lets
|
|
204
|
+
* a category page scope its Bundles tab at all. It was hand-written across
|
|
205
|
+
* all five bundles — and it drifted in B5 the moment the Beyblade X tree
|
|
206
|
+
* changed: two bundles still named `category-x-starters` and
|
|
207
|
+
* `category-x-boosters`, leaves that no longer exist, so those bundles had
|
|
208
|
+
* silently stopped appearing under any Beyblade X category.
|
|
209
|
+
*
|
|
210
|
+
* Worse, the stale strings were still being READ as valid seed ids by
|
|
211
|
+
* `audit-tester-plugin-wiring` R7 once its id scan was widened, which briefly
|
|
212
|
+
* made two genuinely-broken authored tester cases stop being reported. A
|
|
213
|
+
* hand-maintained mirror does not only go wrong — it can keep the check that
|
|
214
|
+
* would have caught it quiet.
|
|
215
|
+
*
|
|
216
|
+
* Deriving from the members' own chains means the union cannot disagree with
|
|
217
|
+
* them, and a member whose chain moves updates every bundle holding it.
|
|
218
|
+
*/
|
|
219
|
+
const PRODUCT_CHAINS = new Map(productsStandardSeedData.map((p) => [String(p.id), (p.categorySlugs ?? [])]));
|
|
220
|
+
function bundleCategorySlugsFor(productIds) {
|
|
221
|
+
const out = [];
|
|
222
|
+
for (const id of productIds) {
|
|
223
|
+
const chain = PRODUCT_CHAINS.get(id);
|
|
224
|
+
/*
|
|
225
|
+
* A member missing from the standard seed is a BUG, not something to shrug
|
|
226
|
+
* past: the bundle would silently lose that member's categories and the
|
|
227
|
+
* Bundles tab would under-report. All eight current members are standard
|
|
228
|
+
* products; if that changes, widen the map rather than softening this.
|
|
229
|
+
*/
|
|
230
|
+
if (!chain)
|
|
231
|
+
throw new Error(`bundle member ${id} is not in productsStandardSeedData`);
|
|
232
|
+
for (const c of chain)
|
|
233
|
+
if (!out.includes(c))
|
|
234
|
+
out.push(c);
|
|
235
|
+
}
|
|
236
|
+
return out;
|
|
237
|
+
}
|
|
307
238
|
const bundleRows = [
|
|
308
239
|
{
|
|
309
240
|
id: "bundle-original-collectors-set",
|
|
@@ -319,7 +250,7 @@ const bundleRows = [
|
|
|
319
250
|
productIds: ["product-beyblade-original-dranzer-s", "product-beyblade-original-driger-v", "product-beyblade-metal-storm-pegasus"],
|
|
320
251
|
},
|
|
321
252
|
bundleProductIds: ["product-beyblade-original-dranzer-s", "product-beyblade-original-driger-v", "product-beyblade-metal-storm-pegasus"],
|
|
322
|
-
bundleCategorySlugs: ["
|
|
253
|
+
bundleCategorySlugs: bundleCategorySlugsFor(["product-beyblade-original-dranzer-s", "product-beyblade-original-driger-v", "product-beyblade-metal-storm-pegasus"]),
|
|
323
254
|
bundleOriginalTotal: 4597, // 1499 + 1799 + 1299
|
|
324
255
|
bundleStockStatus: "in_stock",
|
|
325
256
|
display: { coverImage: seedPhoto("bundle-original-collectors-set-20260101", 1200, 900), showInFooter: false },
|
|
@@ -354,7 +285,7 @@ const bundleRows = [
|
|
|
354
285
|
productIds: ["product-beyblade-metal-storm-pegasus", "product-beyblade-metal-flame-sagittario", "product-beyblade-original-dranzer-s"],
|
|
355
286
|
},
|
|
356
287
|
bundleProductIds: ["product-beyblade-metal-storm-pegasus", "product-beyblade-metal-flame-sagittario", "product-beyblade-original-dranzer-s"],
|
|
357
|
-
bundleCategorySlugs: ["
|
|
288
|
+
bundleCategorySlugs: bundleCategorySlugsFor(["product-beyblade-metal-storm-pegasus", "product-beyblade-metal-flame-sagittario", "product-beyblade-original-dranzer-s"]),
|
|
358
289
|
bundleOriginalTotal: 3997, // 1299 + 1199 + 1499
|
|
359
290
|
bundleStockStatus: "in_stock",
|
|
360
291
|
display: { coverImage: seedPhoto("bundle-metal-fusion-duo-20260101", 1200, 900), showInFooter: false },
|
|
@@ -391,7 +322,7 @@ const bundleRows = [
|
|
|
391
322
|
productIds: ["product-beyblade-burst-valkyrie", "product-beyblade-burst-regalia-genesis", "product-beyblade-metal-flame-sagittario"],
|
|
392
323
|
},
|
|
393
324
|
bundleProductIds: ["product-beyblade-burst-valkyrie", "product-beyblade-burst-regalia-genesis", "product-beyblade-metal-flame-sagittario"],
|
|
394
|
-
bundleCategorySlugs: ["
|
|
325
|
+
bundleCategorySlugs: bundleCategorySlugsFor(["product-beyblade-burst-valkyrie", "product-beyblade-burst-regalia-genesis", "product-beyblade-metal-flame-sagittario"]),
|
|
395
326
|
bundleOriginalTotal: 3597, // 999 + 1399 + 1199
|
|
396
327
|
bundleStockStatus: "in_stock",
|
|
397
328
|
display: { coverImage: seedPhoto("bundle-burst-battlers-pack-20260101", 1200, 900), showInFooter: false },
|
|
@@ -426,7 +357,7 @@ const bundleRows = [
|
|
|
426
357
|
productIds: ["product-beyblade-x-wizard-arrow", "product-beyblade-x-knife-shinobi", "product-beyblade-burst-valkyrie"],
|
|
427
358
|
},
|
|
428
359
|
bundleProductIds: ["product-beyblade-x-wizard-arrow", "product-beyblade-x-knife-shinobi", "product-beyblade-burst-valkyrie"],
|
|
429
|
-
bundleCategorySlugs: ["
|
|
360
|
+
bundleCategorySlugs: bundleCategorySlugsFor(["product-beyblade-x-wizard-arrow", "product-beyblade-x-knife-shinobi", "product-beyblade-burst-valkyrie"]),
|
|
430
361
|
bundleOriginalTotal: 2847, // 899 + 949 + 999
|
|
431
362
|
bundleStockStatus: "in_stock",
|
|
432
363
|
display: { coverImage: seedPhoto("bundle-x-series-starter-20260101", 1200, 900), showInFooter: false },
|
|
@@ -474,7 +405,7 @@ const bundleRows = [
|
|
|
474
405
|
"product-beyblade-burst-valkyrie",
|
|
475
406
|
"product-beyblade-x-wizard-arrow",
|
|
476
407
|
],
|
|
477
|
-
bundleCategorySlugs: ["
|
|
408
|
+
bundleCategorySlugs: bundleCategorySlugsFor(["product-beyblade-original-driger-v", "product-beyblade-metal-storm-pegasus", "product-beyblade-burst-valkyrie", "product-beyblade-x-wizard-arrow"]),
|
|
478
409
|
bundleOriginalTotal: 4996, // 1799 + 1299 + 999 + 899
|
|
479
410
|
bundleStockStatus: "in_stock",
|
|
480
411
|
display: { coverImage: seedPhoto("bundle-every-generation-starter-pack-20260101", 1200, 900), showInFooter: false },
|
|
@@ -496,17 +427,64 @@ const bundleRows = [
|
|
|
496
427
|
updatedAt: daysAgo(1),
|
|
497
428
|
},
|
|
498
429
|
];
|
|
499
|
-
|
|
430
|
+
/*
|
|
431
|
+
* One array, wrapped once.
|
|
432
|
+
*
|
|
433
|
+
* `categoriesSeedData` and `categoriesP1SeedData` were two byte-identical
|
|
434
|
+
* literals, so every change had to be made twice — and `searchTxt` would have
|
|
435
|
+
* been two derivations to keep in step, which is the exact drift
|
|
436
|
+
* `search-txt-wrappers.ts` exists to prevent.
|
|
437
|
+
*
|
|
438
|
+
* 🛑 `withCategorySearchTxt` is applied AFTER the spread, deliberately:
|
|
439
|
+
* `buildCategorySearchTxt` indexes `path` and `ancestors[].name`, both DERIVED
|
|
440
|
+
* by `buildCategoryTree`. Wrapping a row before derivation would index
|
|
441
|
+
* name/slug/description only and silently drop the lineage search — typing
|
|
442
|
+
* "burst" would stop reaching a tier-4 model under Beyblade Burst.
|
|
443
|
+
*/
|
|
444
|
+
const allCategoryRows = [
|
|
500
445
|
...rawCategories.map((c) => ({ ancestors: [], ...c, createdByType: "admin" })),
|
|
501
446
|
...sublistingRows.map((s) => ({ ancestors: [], ...s, createdByType: "admin" })),
|
|
502
447
|
...brandRows.map((b) => ({ ancestors: [], ...b, createdByType: "admin" })),
|
|
503
448
|
...bundleRows.map((b) => ({ ancestors: [], ...b })),
|
|
504
|
-
];
|
|
449
|
+
].map(withCategorySearchTxt);
|
|
450
|
+
/*
|
|
451
|
+
* ── Assertions, at MODULE LOAD, so a silent drop is loud ─────────────────────
|
|
452
|
+
*
|
|
453
|
+
* 🛑 These exist because of a specific failure shape this file is exposed to:
|
|
454
|
+
* `appkit-seed status` counts documents BY ID, so it cannot see content drift
|
|
455
|
+
* at all, and `load` is a `set(…, {merge:true})` that never removes a field.
|
|
456
|
+
* A generated map that silently emptied — `MODEL_LEAVES` keyed on a line id
|
|
457
|
+
* the forest renamed, say — would produce a tree that builds, typechecks,
|
|
458
|
+
* seeds without error, reports perfectly in sync, and is missing 216 pages.
|
|
459
|
+
*
|
|
460
|
+
* Throwing at import is the right severity: every consumer of this module
|
|
461
|
+
* (the seed CLI, the tester fixtures, the audits) wants to fail before writing
|
|
462
|
+
* rather than after.
|
|
463
|
+
*/
|
|
464
|
+
const modelLeafRows = rawCategories.filter((c) => c.tier === 4).length;
|
|
465
|
+
if (modelLeafRows !== MODEL_LEAF_COUNT) {
|
|
466
|
+
throw new Error(`category seed: ${modelLeafRows} tier-4 model leaves reached the tree but ` +
|
|
467
|
+
`category-models.ts generated ${MODEL_LEAF_COUNT}. A line id in the ` +
|
|
468
|
+
`forest no longer matches a key in MODEL_LEAVES — re-run ` +
|
|
469
|
+
`node scripts/gen-category-models.mjs and check the line ids.`);
|
|
470
|
+
}
|
|
471
|
+
if (brandRows.length !== BRAND_ROW_COUNT) {
|
|
472
|
+
throw new Error(`category seed: ${brandRows.length} brand rows built, expected ${BRAND_ROW_COUNT}`);
|
|
473
|
+
}
|
|
474
|
+
{
|
|
475
|
+
/*
|
|
476
|
+
* A price band keyed on a leaf that does not exist is dead weight that READS
|
|
477
|
+
* AS COVERAGE — the hint renders nowhere and nothing says so. Cheap to
|
|
478
|
+
* check, and it is the most likely thing to rot as the tree is edited.
|
|
479
|
+
*/
|
|
480
|
+
const ids = new Set(rawCategories.map((c) => c.id));
|
|
481
|
+
const orphans = MARKET_BAND_LEAF_IDS.filter((id) => !ids.has(id));
|
|
482
|
+
if (orphans.length) {
|
|
483
|
+
throw new Error(`category seed: ${orphans.length} market band(s) key a non-existent ` +
|
|
484
|
+
`leaf: ${orphans.join(", ")}`);
|
|
485
|
+
}
|
|
486
|
+
}
|
|
487
|
+
export const categoriesSeedData = allCategoryRows;
|
|
505
488
|
// P-1 default seed: identical to categoriesSeedData (bundle rows included — P-17
|
|
506
489
|
// re-added them 2026-08-15 after the earlier removal referenced below).
|
|
507
|
-
export const categoriesP1SeedData =
|
|
508
|
-
...rawCategories.map((c) => ({ ancestors: [], ...c, createdByType: "admin" })),
|
|
509
|
-
...sublistingRows.map((s) => ({ ancestors: [], ...s, createdByType: "admin" })),
|
|
510
|
-
...brandRows.map((b) => ({ ancestors: [], ...b, createdByType: "admin" })),
|
|
511
|
-
...bundleRows.map((b) => ({ ancestors: [], ...b })),
|
|
512
|
-
];
|
|
490
|
+
export const categoriesP1SeedData = allCategoryRows;
|
package/dist/seed/index.d.ts
CHANGED
|
@@ -80,6 +80,7 @@ export { scammersSeedData } from "./scammers-seed-data";
|
|
|
80
80
|
export { supportTicketsSeedData } from "./support-tickets-seed-data";
|
|
81
81
|
export { testerChecklistSeedData, testerResponsesSeedData, } from "../features/tester/seed-data";
|
|
82
82
|
export { productFeaturesSeedData } from "./product-features-seed-data";
|
|
83
|
+
export { taxCodesSeedData } from "./tax-codes-seed-data";
|
|
83
84
|
export { offersSeedData } from "./offers-seed-data";
|
|
84
85
|
export { payoutMethodsSeedData, shippingConfigsSeedData, analyticsCardsSeedData, analyticsAlertsSeedData, storeCategoriesSeedData, listingTemplatesSeedData, moderationQueueSeedData, reportsSeedData, itemRequestsSeedData, storeWhatsAppConfigSeedData, storeGoogleConfigSeedData, } from "./store-extensions-seed-data";
|
|
85
86
|
export type { SeedManifest, SeedManifestEntry, SeedCollectionName } from "./manifest";
|
package/dist/seed/index.js
CHANGED
|
@@ -77,6 +77,7 @@ export { testerChecklistSeedData,
|
|
|
77
77
|
// continues, which is exactly what happened on the first attempt here.
|
|
78
78
|
testerResponsesSeedData, } from "../features/tester/seed-data";
|
|
79
79
|
export { productFeaturesSeedData } from "./product-features-seed-data";
|
|
80
|
+
export { taxCodesSeedData } from "./tax-codes-seed-data";
|
|
80
81
|
export { offersSeedData } from "./offers-seed-data";
|
|
81
82
|
// S-STORE Extensions (Tier S-STORE) — 11 new collections
|
|
82
83
|
export { payoutMethodsSeedData, shippingConfigsSeedData, analyticsCardsSeedData, analyticsAlertsSeedData, storeCategoriesSeedData, listingTemplatesSeedData, moderationQueueSeedData, reportsSeedData, itemRequestsSeedData, storeWhatsAppConfigSeedData, storeGoogleConfigSeedData, } from "./store-extensions-seed-data";
|
package/dist/seed/manifest.d.ts
CHANGED
|
@@ -11,6 +11,6 @@ export interface SeedManifestEntry {
|
|
|
11
11
|
type?: string;
|
|
12
12
|
}
|
|
13
13
|
/** Every collection name the CLI seeder (appkit/scripts/seed-cli.mjs) and this manifest cover. */
|
|
14
|
-
export type SeedCollectionName = "users" | "addresses" | "categories" | "stores" | "products" | "orders" | "reviews" | "bids" | "coupons" | "carousels" | "carouselSlides" | "homepageSections" | "siteSettings" | "faqs" | "notifications" | "payouts" | "blogPosts" | "events" | "eventEntries" | "sessions" | "carts" | "wishlists" | "history" | "groupedListings" | "scammerProfiles" | "supportTickets" | "productFeatures" | "offers" | "couponUsage" | "claimedCoupons" | "payoutMethods" | "shippingConfigs" | "analyticsCards" | "analyticsAlerts" | "storeCategories" | "listingTemplates" | "moderationQueue" | "reports" | "itemRequests" | "storeWhatsAppConfig" | "storeGoogleConfig" | "roleOverrides" | "customRoles" | "adminNotifications" | "lotteryEntries" | "shipments" | "shipmentLots" | "shipmentItems" | "catalogueItems" | "testerChecklistItems";
|
|
14
|
+
export type SeedCollectionName = "users" | "addresses" | "categories" | "stores" | "products" | "orders" | "reviews" | "bids" | "coupons" | "carousels" | "carouselSlides" | "homepageSections" | "siteSettings" | "faqs" | "notifications" | "payouts" | "blogPosts" | "events" | "eventEntries" | "sessions" | "carts" | "wishlists" | "history" | "groupedListings" | "scammerProfiles" | "supportTickets" | "productFeatures" | "taxCodes" | "offers" | "couponUsage" | "claimedCoupons" | "payoutMethods" | "shippingConfigs" | "analyticsCards" | "analyticsAlerts" | "storeCategories" | "listingTemplates" | "moderationQueue" | "reports" | "itemRequests" | "storeWhatsAppConfig" | "storeGoogleConfig" | "roleOverrides" | "customRoles" | "adminNotifications" | "lotteryEntries" | "shipments" | "shipmentLots" | "shipmentItems" | "catalogueItems" | "testerChecklistItems";
|
|
15
15
|
export type SeedManifest = Record<SeedCollectionName, SeedManifestEntry[]>;
|
|
16
16
|
export declare const SEED_MANIFEST: SeedManifest;
|