@mohasinac/appkit 4.45.0 → 4.47.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/server/features/categories/live-counts.d.ts +41 -0
- package/dist/_internal/server/features/categories/live-counts.js +72 -0
- package/dist/_internal/server/features/checkout/expand-order-items.js +17 -0
- package/dist/_internal/server/features/orders/adapters.js +29 -0
- package/dist/_internal/server/features/parts/assembly.d.ts +126 -0
- package/dist/_internal/server/features/parts/assembly.js +246 -0
- package/dist/_internal/server/features/parts/data.d.ts +65 -0
- package/dist/_internal/server/features/parts/data.js +170 -0
- package/dist/_internal/server/features/parts/og.d.ts +34 -0
- package/dist/_internal/server/features/parts/og.js +66 -0
- package/dist/_internal/server/features/search/search-everything.d.ts +75 -0
- package/dist/_internal/server/features/search/search-everything.js +120 -0
- package/dist/_internal/server/features/search/search-sources.d.ts +17 -0
- package/dist/_internal/server/features/search/search-sources.js +138 -0
- package/dist/_internal/server/jobs/core/categoryTaxonomyBackfill.d.ts +70 -0
- package/dist/_internal/server/jobs/core/categoryTaxonomyBackfill.js +302 -0
- package/dist/_internal/server/jobs/core/countersReconcile.js +128 -19
- package/dist/_internal/server/jobs/core/jobRunners.js +12 -0
- package/dist/_internal/server/jobs/core/onCategoryWrite.js +73 -3
- package/dist/_internal/server/jobs/core/onProductWrite.js +93 -43
- package/dist/_internal/server/jobs/core/productViewWindows.d.ts +2 -0
- package/dist/_internal/server/jobs/core/productViewWindows.js +173 -0
- package/dist/_internal/server/jobs/core/testDataCleanup.d.ts +1 -1
- package/dist/_internal/server/jobs/core/testDataCleanup.js +27 -1
- package/dist/_internal/server/jobs/handlers/pageViewRollup.d.ts +20 -0
- package/dist/_internal/server/jobs/handlers/pageViewRollup.js +25 -1
- package/dist/_internal/shared/actions/action-registry.js +36 -0
- package/dist/_internal/shared/features/promotions/schema.d.ts +2 -2
- package/dist/_internal/shared/features/shipping/protections.d.ts +93 -1
- package/dist/_internal/shared/features/shipping/protections.js +127 -0
- package/dist/_internal/shared/search/part-aliases.d.ts +44 -0
- package/dist/_internal/shared/search/part-aliases.js +104 -0
- package/dist/constants/api-endpoints.d.ts +18 -12
- package/dist/constants/api-endpoints.js +6 -8
- package/dist/constants/field-names.d.ts +0 -2
- package/dist/constants/field-names.js +0 -2
- package/dist/constants/index.d.ts +1 -1
- package/dist/constants/index.js +1 -1
- package/dist/core/index.d.ts +0 -3
- package/dist/core/index.js +0 -2
- package/dist/errors/messages.d.ts +0 -7
- package/dist/errors/messages.js +0 -8
- package/dist/features/admin/components/AdminSectionsView.js +83 -32
- package/dist/features/admin/components/AdminSiteSettingsView.js +6 -2
- package/dist/features/admin/components/sections/sectionTemplates.d.ts +117 -0
- package/dist/features/admin/components/sections/sectionTemplates.js +213 -0
- package/dist/features/admin/schemas/firestore.d.ts +15 -0
- package/dist/features/admin/types/index.d.ts +0 -1
- package/dist/features/auth/permissions/constants.d.ts +1 -1
- package/dist/features/auth/permissions/constants.js +0 -2
- package/dist/features/categories/api/[id]/description-template/route.js +40 -1
- package/dist/features/categories/components/BrandDetailPageView.js +14 -3
- package/dist/features/categories/components/BreadcrumbTrail.d.ts +24 -1
- package/dist/features/categories/components/BreadcrumbTrail.js +45 -4
- package/dist/features/categories/components/CategoryDetailPageView.js +89 -14
- package/dist/features/categories/components/CategoryTemplatePicker.js +9 -1
- package/dist/features/categories/repository/categories.repository.d.ts +65 -0
- package/dist/features/categories/repository/categories.repository.js +168 -22
- package/dist/features/categories/repository/category-counts.d.ts +143 -0
- package/dist/features/categories/repository/category-counts.js +224 -0
- package/dist/features/categories/schemas/category-counts.d.ts +155 -0
- package/dist/features/categories/schemas/category-counts.js +116 -0
- package/dist/features/categories/schemas/firestore.d.ts +0 -2
- package/dist/features/categories/schemas/firestore.js +0 -1
- package/dist/features/categories/utils/breadcrumb-chain.d.ts +194 -0
- package/dist/features/categories/utils/breadcrumb-chain.js +202 -0
- package/dist/features/corporate/schemas/index.d.ts +4 -4
- package/dist/features/orders/schemas/firestore.d.ts +13 -0
- package/dist/features/orders/types/index.d.ts +30 -0
- package/dist/features/parts/components/AssemblyDrawer.d.ts +107 -0
- package/dist/features/parts/components/AssemblyDrawer.js +250 -0
- package/dist/features/parts/components/ModelCompositionPanel.d.ts +37 -0
- package/dist/features/parts/components/ModelCompositionPanel.js +34 -0
- package/dist/features/parts/components/PartDetailPageView.d.ts +48 -0
- package/dist/features/parts/components/PartDetailPageView.js +20 -0
- package/dist/features/parts/components/PartsIndexPageView.d.ts +33 -0
- package/dist/features/parts/components/PartsIndexPageView.js +17 -0
- package/dist/features/parts/schemas/firestore.d.ts +257 -0
- package/dist/features/parts/schemas/firestore.js +181 -0
- package/dist/features/parts/utils/colourway-match.d.ts +62 -0
- package/dist/features/parts/utils/colourway-match.js +98 -0
- package/dist/features/parts/utils/part-breakdown.d.ts +53 -0
- package/dist/features/parts/utils/part-breakdown.js +77 -0
- package/dist/features/products/components/ProductDetailPageView.d.ts +14 -1
- package/dist/features/products/components/ProductDetailPageView.js +22 -3
- package/dist/features/products/components/ProductTabsShell.d.ts +3 -1
- package/dist/features/products/components/ProductTabsShell.js +4 -1
- package/dist/features/products/repository/category-assignment.d.ts +132 -0
- package/dist/features/products/repository/category-assignment.js +304 -0
- package/dist/features/products/repository/products.repository.d.ts +58 -1
- package/dist/features/products/repository/products.repository.js +255 -28
- package/dist/features/products/schemas/firestore.d.ts +147 -6
- package/dist/features/products/schemas/firestore.js +49 -0
- package/dist/features/search/components/SearchResultsPageView.d.ts +33 -0
- package/dist/features/search/components/SearchResultsPageView.js +21 -0
- package/dist/features/seller/components/CategoryInlineSelect.d.ts +17 -1
- package/dist/features/seller/components/CategoryInlineSelect.js +14 -3
- package/dist/features/seller/components/SellerProductShell.d.ts +26 -1
- package/dist/features/seller/components/SellerProductShell.js +30 -1
- package/dist/features/seller/components/SellerProductsView.js +129 -3
- package/dist/features/tester/seed-data/authored/admin__site-system.js +0 -15
- package/dist/features/tester/seed-data/authored/content-discovery__parts-database.d.ts +2 -0
- package/dist/features/tester/seed-data/authored/content-discovery__parts-database.js +166 -0
- package/dist/features/tester/seed-data/authored/index.js +2 -0
- package/dist/features/tester/seed-data/tester-checklist-seed-data.js +28 -1
- package/dist/index.d.ts +11 -16
- package/dist/index.js +26 -18
- package/dist/next/routing/route-map.d.ts +6 -2
- package/dist/next/routing/route-map.js +14 -1
- package/dist/providers/db-firebase/base.repository.d.ts +31 -0
- package/dist/providers/db-firebase/base.repository.js +31 -0
- package/dist/repositories/index.d.ts +1 -2
- package/dist/repositories/index.js +0 -1
- package/dist/seed/_helpers/category-content-generated.d.ts +24 -0
- package/dist/seed/_helpers/category-content-generated.js +1837 -0
- package/dist/seed/_helpers/category-content.d.ts +29 -0
- package/dist/seed/_helpers/category-content.js +98 -1
- package/dist/seed/_helpers/category-forest.js +104 -24
- package/dist/seed/_helpers/category-parts.d.ts +9 -0
- package/dist/seed/_helpers/category-parts.js +4594 -0
- package/dist/seed/carousel-slides-seed-data.js +81 -8
- package/dist/seed/categories-seed-data.js +44 -6
- package/dist/seed/homepage-sections-seed-data.js +170 -9
- package/dist/seed/index.d.ts +2 -0
- package/dist/seed/index.js +13 -0
- package/dist/seed/model-composition-seed-data.d.ts +15 -0
- package/dist/seed/model-composition-seed-data.js +8918 -0
- package/dist/seed/part-glossary-seed-data.d.ts +15 -0
- package/dist/seed/part-glossary-seed-data.js +10729 -0
- package/dist/seed/products-standard-seed-data.js +52 -0
- package/dist/seed/seed-purpose.d.ts +103 -4
- package/dist/seed/seed-purpose.js +162 -11
- package/dist/seo/json-ld.d.ts +10 -1
- package/dist/seo/json-ld.js +9 -1
- package/dist/server.d.ts +7 -0
- package/dist/server.js +11 -0
- package/dist/styles.css +1 -1
- package/dist/tailwind-utilities.css +1 -1
- package/dist/utils/id-generators.d.ts +30 -0
- package/dist/utils/id-generators.js +31 -1
- package/dist/utils/search-txt-builders.js +22 -0
- package/dist/utils/search-txt.d.ts +22 -0
- package/dist/utils/search-txt.js +17 -0
- package/dist/values/success-messages.d.ts +0 -4
- package/dist/values/success-messages.js +0 -5
- package/package.json +1 -1
- package/scripts/seed-cli.mjs +292 -16
- package/dist/core/copilot-log.repository.d.ts +0 -44
- package/dist/core/copilot-log.repository.js +0 -61
- package/dist/features/copilot/components/AdminCopilotView.d.ts +0 -21
- package/dist/features/copilot/components/AdminCopilotView.js +0 -51
- package/dist/features/copilot/components/index.d.ts +0 -2
- package/dist/features/copilot/components/index.js +0 -1
- package/dist/features/copilot/hooks/useCopilotChat.d.ts +0 -25
- package/dist/features/copilot/hooks/useCopilotChat.js +0 -65
- package/dist/features/copilot/hooks/useCopilotFeedback.d.ts +0 -15
- package/dist/features/copilot/hooks/useCopilotFeedback.js +0 -15
- package/dist/features/copilot/index.d.ts +0 -3
- package/dist/features/copilot/index.js +0 -3
|
@@ -656,6 +656,58 @@ const _rawProductsStandardSeedData = [
|
|
|
656
656
|
createdAt: new Date("2026-08-24"),
|
|
657
657
|
updatedAt: new Date("2026-08-24"),
|
|
658
658
|
},
|
|
659
|
+
/*
|
|
660
|
+
* B6 — THE ONLY 18% GST ROW IN THE CATALOGUE.
|
|
661
|
+
*
|
|
662
|
+
* 🛑 `tax-hsn-95030010` (electronic toys, 18%) has existed as a `taxCodes`
|
|
663
|
+
* row since B3 with nothing referencing it, so the 18% arm of
|
|
664
|
+
* `calculateGst`, `gstByRate` (C0c defect 4), the per-rate invoice table and
|
|
665
|
+
* the blended-rate payout all had NO DATA that could exercise them. Four
|
|
666
|
+
* code paths that cannot be wrong because nothing reaches them.
|
|
667
|
+
*
|
|
668
|
+
* A BeyPointer is a battery-powered scoring device, which is what 9503 0010
|
|
669
|
+
* covers — so this is a genuinely electronic good rather than a flag planted
|
|
670
|
+
* on a convenient row.
|
|
671
|
+
*
|
|
672
|
+
* 🛑 No `gstRate`/`hsnCode` HERE, deliberately. They derive from
|
|
673
|
+
* `category-accessory-counters` through `deriveTaxonomy`, which is the path
|
|
674
|
+
* that will carry them in production. Hand-writing 18 would satisfy step 1 of
|
|
675
|
+
* the resolution order and leave the DERIVATION untested — a fixture that
|
|
676
|
+
* passes while the mechanism it exists to prove is never run, which is the
|
|
677
|
+
* shape of Root Cause #101.
|
|
678
|
+
*
|
|
679
|
+
* It is also what makes a MIXED-RATE cart possible for the first time: this
|
|
680
|
+
* row at 18% beside any bey at 5% is the one case C0c's §13 check 6b asks for
|
|
681
|
+
* and the catalogue could not previously express.
|
|
682
|
+
*/
|
|
683
|
+
{
|
|
684
|
+
id: "product-beypointer-digital-battle-counter",
|
|
685
|
+
slug: "product-beypointer-digital-battle-counter",
|
|
686
|
+
barcodeId: "LIR-BEY-ACC-018",
|
|
687
|
+
title: "BeyPointer Digital Battle Counter",
|
|
688
|
+
description: "Battery-powered digital battle counter used for tournament scoring — tracks points across a best-of series, with a reset and a hold. Takes two AAA cells (not included). An electronic accessory, so it is taxed at the 18% electronic-toy rate rather than the 5% that applies to tops and parts.",
|
|
689
|
+
categorySlugs: ["category-accessory-counters", "category-accessories", "category-spinning-tops"],
|
|
690
|
+
categoryNames: ["Counters & BeyPointers", "Accessories", "Spinning Tops"],
|
|
691
|
+
brandSlug: "brand-takara-tomy",
|
|
692
|
+
brand: "Takara-Tomy",
|
|
693
|
+
price: 1299,
|
|
694
|
+
currency: "INR",
|
|
695
|
+
stockQuantity: 6,
|
|
696
|
+
availableQuantity: 6,
|
|
697
|
+
isSold: false,
|
|
698
|
+
mainImage: seedPhoto("product-image-beypointer-digital-battle-counter-1-20261012", 900, 900),
|
|
699
|
+
images: [
|
|
700
|
+
seedPhoto("product-image-beypointer-digital-battle-counter-1-20261012", 900, 900),
|
|
701
|
+
seedPhoto("product-image-beypointer-digital-battle-counter-2-20261012", 900, 900),
|
|
702
|
+
],
|
|
703
|
+
status: PRODUCT_FIELDS.STATUS_VALUES.PUBLISHED,
|
|
704
|
+
condition: PRODUCT_FIELDS.CONDITION_VALUES.NEW,
|
|
705
|
+
tags: ["accessory"],
|
|
706
|
+
storeId: "store-beyblade-arena",
|
|
707
|
+
storeName: "Beyblade Arena",
|
|
708
|
+
createdAt: new Date("2026-10-12"),
|
|
709
|
+
updatedAt: new Date("2026-10-12"),
|
|
710
|
+
},
|
|
659
711
|
];
|
|
660
712
|
export const productsStandardSeedData = [
|
|
661
713
|
..._rawProductsStandardSeedData.map((p) => withTokens({
|
|
@@ -133,15 +133,111 @@ export declare const PURGEABLE: Record<SeedPurpose, boolean>;
|
|
|
133
133
|
* command, and the cost of being unable to clear it is the state this replaced.
|
|
134
134
|
*/
|
|
135
135
|
export declare const SEED_COLLECTION_PURPOSE: Record<string, SeedPurpose>;
|
|
136
|
+
/**
|
|
137
|
+
* Individual rows that differ from their collection's default, by document id.
|
|
138
|
+
*
|
|
139
|
+
* 🛑 This is the "clear all out EXCEPT our admin creds and the original single
|
|
140
|
+
* store" instruction, made declarative. Both collections default to `test` —
|
|
141
|
+
* every persona and every other store is disposable — and exactly two rows are
|
|
142
|
+
* not.
|
|
143
|
+
*
|
|
144
|
+
* ✅ The precedent already existed and this replaces it: `cmdDelete` has
|
|
145
|
+
* carried `PROTECTED_USER_UIDS = new Set(["user-admin-letitrip"])` for a long
|
|
146
|
+
* time, skipping that uid on a wholesale delete. One mechanism is better than a
|
|
147
|
+
* list the purge path happens to know about privately.
|
|
148
|
+
*
|
|
149
|
+
* 🛑 Keyed **`"<collection>:<id>"`**, and the collection half is load-bearing.
|
|
150
|
+
*
|
|
151
|
+
* It was a bare id, and a live read-back caught the consequence: `carts`,
|
|
152
|
+
* `wishlists` and `history` are all keyed on the USER id (CLAUDE.md: "Doc id =
|
|
153
|
+
* userId"), so `"user-admin-letitrip"` matched the admin's **cart** as well as
|
|
154
|
+
* the admin's user row — making a disposable cart permanently unpurgeable.
|
|
155
|
+
* Subtle, silent, and it would have left a stale cart surviving every purge
|
|
156
|
+
* forever.
|
|
157
|
+
*
|
|
158
|
+
* The id half is the DOCUMENT id, which for `users` is the Auth uid (`uid` on
|
|
159
|
+
* the seed row) and for everything else is `id`. `resolveSeedPurpose` checks
|
|
160
|
+
* both, because a seeded user row carries `uid` and no `id`.
|
|
161
|
+
*/
|
|
162
|
+
export declare const SEED_ROW_PURPOSE: Record<string, SeedPurpose>;
|
|
163
|
+
/**
|
|
164
|
+
* Collections whose purpose FOLLOWS a referenced row rather than being declared.
|
|
165
|
+
*
|
|
166
|
+
* 🛑 This exists to hold one invariant: **a `permanent` row may not reference a
|
|
167
|
+
* `test` row**, because the purge would leave it behind as a dangling FK that
|
|
168
|
+
* still looks like real config.
|
|
169
|
+
*
|
|
170
|
+
* Measured in `store-extensions-seed-data.ts`: **14** rows reference
|
|
171
|
+
* `store-beyblade-arena` (test) and **9** reference `store-letitrip-official`
|
|
172
|
+
* (permanent). So marking `shippingConfigs` / `analyticsCards` permanent at the
|
|
173
|
+
* COLLECTION level — which the user's list implies and an earlier draft of the
|
|
174
|
+
* plan did — would strand 14 rows pointing at a store the purge deletes.
|
|
175
|
+
*
|
|
176
|
+
* Deriving is better than 23 hand-maintained per-row overrides for the usual
|
|
177
|
+
* reason: a hand-kept mirror drifts the moment a store is added (Root Cause
|
|
178
|
+
* #42), and a derivation is correct for stores that do not exist yet.
|
|
179
|
+
*/
|
|
180
|
+
export declare const SEED_PURPOSE_DERIVED_FROM: Record<string, {
|
|
181
|
+
readonly field: SeedReferenceField;
|
|
182
|
+
readonly collection: string;
|
|
183
|
+
}>;
|
|
136
184
|
/** A seeded row may override its collection's default inline. */
|
|
137
185
|
export interface WithSeedPurpose {
|
|
138
186
|
seedPurpose?: SeedPurpose;
|
|
139
187
|
}
|
|
140
188
|
/**
|
|
141
|
-
* The
|
|
142
|
-
*
|
|
189
|
+
* The fields `resolveSeedPurpose` reads off a row — all optional, because it is
|
|
190
|
+
* handed rows from 47 differently-shaped collections.
|
|
191
|
+
*
|
|
192
|
+
* 🛑 Declared explicitly rather than as `Record<string, unknown>`, which
|
|
193
|
+
* `audit-unknown-leakage` blocks and which is worse anyway: an index signature
|
|
194
|
+
* accepts ANY field name, so a typo in `SEED_PURPOSE_DERIVED_FROM.field` would
|
|
195
|
+
* read `undefined` and fall through to `test` silently — the exact mistake
|
|
196
|
+
* already made once, where `analyticsCards` was declared to derive from
|
|
197
|
+
* `storeId` and the rows carry `ownerId`.
|
|
198
|
+
*
|
|
199
|
+
* `SeedReferenceField` then constrains that `field` to one of these, so the
|
|
200
|
+
* same typo becomes a compile error.
|
|
201
|
+
*/
|
|
202
|
+
export interface SeedRowIdentity extends WithSeedPurpose {
|
|
203
|
+
/** Document id, on every collection except `users`. */
|
|
204
|
+
id?: string;
|
|
205
|
+
/** `users` seed rows carry the Auth uid and no `id`. */
|
|
206
|
+
uid?: string;
|
|
207
|
+
storeId?: string;
|
|
208
|
+
ownerId?: string;
|
|
209
|
+
userId?: string;
|
|
210
|
+
sellerId?: string;
|
|
211
|
+
}
|
|
212
|
+
/** The reference fields a derived collection may follow. */
|
|
213
|
+
export type SeedReferenceField = "storeId" | "ownerId" | "userId" | "sellerId";
|
|
214
|
+
/**
|
|
215
|
+
* Resolution order, most specific first:
|
|
216
|
+
*
|
|
217
|
+
* 1. the row's own inline `seedPurpose`
|
|
218
|
+
* 2. a per-row override in `SEED_ROW_PURPOSE`, by `id` or `uid`
|
|
219
|
+
* 3. for a DERIVED collection, the purpose of the row it references
|
|
220
|
+
* 4. the collection default
|
|
221
|
+
* 5. `test`
|
|
222
|
+
*
|
|
223
|
+
* 🛑 Step 3 is what holds the referential invariant. A `shippingConfig` for
|
|
224
|
+
* `store-letitrip-official` resolves `permanent`; one for
|
|
225
|
+
* `store-beyblade-arena` resolves `test` and is purged alongside the store it
|
|
226
|
+
* configures, rather than outliving it.
|
|
227
|
+
*/
|
|
228
|
+
export declare function resolveSeedPurpose(collection: string, row?: SeedRowIdentity | null,
|
|
229
|
+
/**
|
|
230
|
+
* The document id, when the caller has it and the row does not.
|
|
231
|
+
*
|
|
232
|
+
* 🛑 Required by `loadGeneric`, which does `let { id, ...data } = docData`
|
|
233
|
+
* and therefore hands this function a row with **no `id` at all**. Without
|
|
234
|
+
* this parameter the per-row overrides silently did not apply: measured,
|
|
235
|
+
* `store-letitrip-official` loaded as `test` — i.e. the platform's own store
|
|
236
|
+
* would have been purged, which is exactly the row the keep-list exists to
|
|
237
|
+
* protect. `loadUsers` was unaffected because `{ ...u }` keeps `uid`, which
|
|
238
|
+
* is why the admin override worked and the store one did not.
|
|
143
239
|
*/
|
|
144
|
-
|
|
240
|
+
docId?: string | null): SeedPurpose;
|
|
145
241
|
export interface SeedPurposeFields {
|
|
146
242
|
seedPurpose: SeedPurpose;
|
|
147
243
|
}
|
|
@@ -167,7 +263,10 @@ export declare function seedPurposeFields(purpose: SeedPurpose): SeedPurposeFiel
|
|
|
167
263
|
* Returns a NEW object; the seed arrays are module-level constants re-imported
|
|
168
264
|
* on every CLI invocation and must not be mutated.
|
|
169
265
|
*/
|
|
170
|
-
export declare function stampSeedPurpose<T extends WithSeedPurpose>(collection: string, row: T
|
|
266
|
+
export declare function stampSeedPurpose<T extends WithSeedPurpose>(collection: string, row: T,
|
|
267
|
+
/** Pass this whenever the caller holds the id and the row does not — see
|
|
268
|
+
* `resolveSeedPurpose`'s `docId` for why `loadGeneric` must. */
|
|
269
|
+
docId?: string | null): T & SeedPurposeFields;
|
|
171
270
|
/**
|
|
172
271
|
* Mark a whole array as something other than its collection default — the
|
|
173
272
|
* readable way to say "this file is permanent" (or "this file is fixtures")
|
|
@@ -131,6 +131,23 @@ export const SEED_COLLECTION_PURPOSE = {
|
|
|
131
131
|
/* Branding, fees, commissions, watermark, legal pages, and the AES-encrypted
|
|
132
132
|
* credentials. 🛑 A purge here would destroy live API keys. */
|
|
133
133
|
siteSettings: "permanent",
|
|
134
|
+
/*
|
|
135
|
+
* ── permanent, second tranche: the site's own CONTENT and COMPOSITION ────
|
|
136
|
+
*
|
|
137
|
+
* Added on the user's explicit list. The same test applies and they pass it:
|
|
138
|
+
* clearing any of these means re-AUTHORING, not re-running a loader.
|
|
139
|
+
*
|
|
140
|
+
* 🛑 But "permanent" obliges us to make them REAL — see G1c in the plan.
|
|
141
|
+
* Marking a placeholder permanent only makes the placeholder unpurgeable, so
|
|
142
|
+
* each of these has to trace to a crawl source: blogPosts to the buying/rules
|
|
143
|
+
* guide split, homepageSections to worldhobbyshop's generation tiles and
|
|
144
|
+
* 25-link systems matrix, carouselSlides to beybladeartshop's 5-slide hero
|
|
145
|
+
* including the anti-counterfeit slide.
|
|
146
|
+
*/
|
|
147
|
+
blogPosts: "permanent",
|
|
148
|
+
carousels: "permanent",
|
|
149
|
+
carouselSlides: "permanent",
|
|
150
|
+
homepageSections: "permanent",
|
|
134
151
|
/*
|
|
135
152
|
* 🛑 The tester checklist cases are PERMANENT CONTENT, not scaffolding.
|
|
136
153
|
*
|
|
@@ -145,6 +162,15 @@ export const SEED_COLLECTION_PURPOSE = {
|
|
|
145
162
|
* default is for.
|
|
146
163
|
*/
|
|
147
164
|
testerChecklistItems: "permanent",
|
|
165
|
+
/*
|
|
166
|
+
* E4/D12 — PERMANENT. These are extracted from a corpus by committed
|
|
167
|
+
* scripts, not fixtures: clearing them means re-running an extraction over a
|
|
168
|
+
* 7 GB local crawl, which is the test § G1c sets ("if clearing it means
|
|
169
|
+
* somebody has to WRITE it again rather than re-run a loader"). Re-running
|
|
170
|
+
* the extractor is possible but needs a corpus a clone will not have.
|
|
171
|
+
*/
|
|
172
|
+
partGlossary: "permanent",
|
|
173
|
+
modelComposition: "permanent",
|
|
148
174
|
testerChecklistResponses: "test",
|
|
149
175
|
/*
|
|
150
176
|
* ── test: everything else ───────────────────────────────────────────────
|
|
@@ -172,20 +198,14 @@ export const SEED_COLLECTION_PURPOSE = {
|
|
|
172
198
|
notifications: "test",
|
|
173
199
|
supportTickets: "test",
|
|
174
200
|
catalogueItems: "test",
|
|
175
|
-
blogPosts: "test",
|
|
176
201
|
events: "test",
|
|
177
202
|
eventEntries: "test",
|
|
178
203
|
groupedListings: "test",
|
|
179
204
|
scammerProfiles: "test",
|
|
180
|
-
carousels: "test",
|
|
181
|
-
carouselSlides: "test",
|
|
182
|
-
homepageSections: "test",
|
|
183
205
|
procurementShipments: "test",
|
|
184
206
|
shipmentLots: "test",
|
|
185
207
|
shipmentItems: "test",
|
|
186
208
|
payoutMethods: "test",
|
|
187
|
-
shippingConfigs: "test",
|
|
188
|
-
analyticsCards: "test",
|
|
189
209
|
analyticsAlerts: "test",
|
|
190
210
|
storeCategories: "test",
|
|
191
211
|
listingTemplates: "test",
|
|
@@ -195,12 +215,131 @@ export const SEED_COLLECTION_PURPOSE = {
|
|
|
195
215
|
storeGoogleConfig: "test",
|
|
196
216
|
};
|
|
197
217
|
/**
|
|
198
|
-
*
|
|
199
|
-
*
|
|
218
|
+
* Individual rows that differ from their collection's default, by document id.
|
|
219
|
+
*
|
|
220
|
+
* 🛑 This is the "clear all out EXCEPT our admin creds and the original single
|
|
221
|
+
* store" instruction, made declarative. Both collections default to `test` —
|
|
222
|
+
* every persona and every other store is disposable — and exactly two rows are
|
|
223
|
+
* not.
|
|
224
|
+
*
|
|
225
|
+
* ✅ The precedent already existed and this replaces it: `cmdDelete` has
|
|
226
|
+
* carried `PROTECTED_USER_UIDS = new Set(["user-admin-letitrip"])` for a long
|
|
227
|
+
* time, skipping that uid on a wholesale delete. One mechanism is better than a
|
|
228
|
+
* list the purge path happens to know about privately.
|
|
229
|
+
*
|
|
230
|
+
* 🛑 Keyed **`"<collection>:<id>"`**, and the collection half is load-bearing.
|
|
231
|
+
*
|
|
232
|
+
* It was a bare id, and a live read-back caught the consequence: `carts`,
|
|
233
|
+
* `wishlists` and `history` are all keyed on the USER id (CLAUDE.md: "Doc id =
|
|
234
|
+
* userId"), so `"user-admin-letitrip"` matched the admin's **cart** as well as
|
|
235
|
+
* the admin's user row — making a disposable cart permanently unpurgeable.
|
|
236
|
+
* Subtle, silent, and it would have left a stale cart surviving every purge
|
|
237
|
+
* forever.
|
|
238
|
+
*
|
|
239
|
+
* The id half is the DOCUMENT id, which for `users` is the Auth uid (`uid` on
|
|
240
|
+
* the seed row) and for everything else is `id`. `resolveSeedPurpose` checks
|
|
241
|
+
* both, because a seeded user row carries `uid` and no `id`.
|
|
242
|
+
*/
|
|
243
|
+
export const SEED_ROW_PURPOSE = {
|
|
244
|
+
/* Our own admin account. Clearing it locks us out of our own site — and the
|
|
245
|
+
* Auth record is the half most easily missed, since `appkit-seed delete` does
|
|
246
|
+
* not touch Firebase Auth at all. */
|
|
247
|
+
"users:user-admin-letitrip": "permanent",
|
|
248
|
+
/* The platform's own default store. The other three seeded stores
|
|
249
|
+
* (beyblade-arena, blader-bazaar, vintage-vault-co) are test fixtures. */
|
|
250
|
+
"stores:store-letitrip-official": "permanent",
|
|
251
|
+
};
|
|
252
|
+
/**
|
|
253
|
+
* Collections whose purpose FOLLOWS a referenced row rather than being declared.
|
|
254
|
+
*
|
|
255
|
+
* 🛑 This exists to hold one invariant: **a `permanent` row may not reference a
|
|
256
|
+
* `test` row**, because the purge would leave it behind as a dangling FK that
|
|
257
|
+
* still looks like real config.
|
|
258
|
+
*
|
|
259
|
+
* Measured in `store-extensions-seed-data.ts`: **14** rows reference
|
|
260
|
+
* `store-beyblade-arena` (test) and **9** reference `store-letitrip-official`
|
|
261
|
+
* (permanent). So marking `shippingConfigs` / `analyticsCards` permanent at the
|
|
262
|
+
* COLLECTION level — which the user's list implies and an earlier draft of the
|
|
263
|
+
* plan did — would strand 14 rows pointing at a store the purge deletes.
|
|
264
|
+
*
|
|
265
|
+
* Deriving is better than 23 hand-maintained per-row overrides for the usual
|
|
266
|
+
* reason: a hand-kept mirror drifts the moment a store is added (Root Cause
|
|
267
|
+
* #42), and a derivation is correct for stores that do not exist yet.
|
|
268
|
+
*/
|
|
269
|
+
export const SEED_PURPOSE_DERIVED_FROM = {
|
|
270
|
+
shippingConfigs: { field: "storeId", collection: "stores" },
|
|
271
|
+
/*
|
|
272
|
+
* 🛑 `ownerId` → `users`, NOT `storeId` → `stores`. I wrote the store form
|
|
273
|
+
* first and the data disproved it: measured, `analyticsCardsSeedData` carries
|
|
274
|
+
* **zero** `storeId` fields — every row is `{ scope: "seller", ownerId:
|
|
275
|
+
* "user-tyson-blader", … }`. So these follow the USER whose dashboard they
|
|
276
|
+
* compose, and since that user is a test persona all nine correctly resolve
|
|
277
|
+
* `test`.
|
|
278
|
+
*
|
|
279
|
+
* Worth recording because the wrong field does not error: a derived row whose
|
|
280
|
+
* named field is absent falls to `test`, which happened to be the right
|
|
281
|
+
* answer here and would have silently been the WRONG one for a card owned by
|
|
282
|
+
* the admin.
|
|
283
|
+
*/
|
|
284
|
+
analyticsCards: { field: "ownerId", collection: "users" },
|
|
285
|
+
};
|
|
286
|
+
/**
|
|
287
|
+
* Resolution order, most specific first:
|
|
288
|
+
*
|
|
289
|
+
* 1. the row's own inline `seedPurpose`
|
|
290
|
+
* 2. a per-row override in `SEED_ROW_PURPOSE`, by `id` or `uid`
|
|
291
|
+
* 3. for a DERIVED collection, the purpose of the row it references
|
|
292
|
+
* 4. the collection default
|
|
293
|
+
* 5. `test`
|
|
294
|
+
*
|
|
295
|
+
* 🛑 Step 3 is what holds the referential invariant. A `shippingConfig` for
|
|
296
|
+
* `store-letitrip-official` resolves `permanent`; one for
|
|
297
|
+
* `store-beyblade-arena` resolves `test` and is purged alongside the store it
|
|
298
|
+
* configures, rather than outliving it.
|
|
200
299
|
*/
|
|
201
|
-
export function resolveSeedPurpose(collection, row
|
|
300
|
+
export function resolveSeedPurpose(collection, row,
|
|
301
|
+
/**
|
|
302
|
+
* The document id, when the caller has it and the row does not.
|
|
303
|
+
*
|
|
304
|
+
* 🛑 Required by `loadGeneric`, which does `let { id, ...data } = docData`
|
|
305
|
+
* and therefore hands this function a row with **no `id` at all**. Without
|
|
306
|
+
* this parameter the per-row overrides silently did not apply: measured,
|
|
307
|
+
* `store-letitrip-official` loaded as `test` — i.e. the platform's own store
|
|
308
|
+
* would have been purged, which is exactly the row the keep-list exists to
|
|
309
|
+
* protect. `loadUsers` was unaffected because `{ ...u }` keeps `uid`, which
|
|
310
|
+
* is why the admin override worked and the store one did not.
|
|
311
|
+
*/
|
|
312
|
+
docId) {
|
|
202
313
|
if (row && isSeedPurpose(row.seedPurpose))
|
|
203
314
|
return row.seedPurpose;
|
|
315
|
+
/* `"<collection>:<id>"` — the collection half is what stops a users
|
|
316
|
+
* override also matching that user's CART, which is keyed on the same id. */
|
|
317
|
+
const rowKey = (coll, id) => `${coll}:${id}`;
|
|
318
|
+
if (docId && isSeedPurpose(SEED_ROW_PURPOSE[rowKey(collection, docId)]))
|
|
319
|
+
return SEED_ROW_PURPOSE[rowKey(collection, docId)];
|
|
320
|
+
if (row) {
|
|
321
|
+
/* `users` seed rows carry `uid` and no `id`; everything else carries `id`. */
|
|
322
|
+
const ownId = typeof row.id === "string" ? row.id : typeof row.uid === "string" ? row.uid : null;
|
|
323
|
+
if (ownId && isSeedPurpose(SEED_ROW_PURPOSE[rowKey(collection, ownId)]))
|
|
324
|
+
return SEED_ROW_PURPOSE[rowKey(collection, ownId)];
|
|
325
|
+
const derived = SEED_PURPOSE_DERIVED_FROM[collection];
|
|
326
|
+
if (derived) {
|
|
327
|
+
const refId = row[derived.field];
|
|
328
|
+
if (typeof refId === "string") {
|
|
329
|
+
const refOverride = SEED_ROW_PURPOSE[rowKey(derived.collection, refId)];
|
|
330
|
+
if (isSeedPurpose(refOverride))
|
|
331
|
+
return refOverride;
|
|
332
|
+
const refDefault = SEED_COLLECTION_PURPOSE[derived.collection];
|
|
333
|
+
return isSeedPurpose(refDefault) ? refDefault : "test";
|
|
334
|
+
}
|
|
335
|
+
/*
|
|
336
|
+
* A derived row with no reference cannot inherit, and guessing
|
|
337
|
+
* `permanent` would be the dangling-FK failure this map exists to
|
|
338
|
+
* prevent. `test` is the safe answer: it is re-seedable.
|
|
339
|
+
*/
|
|
340
|
+
return "test";
|
|
341
|
+
}
|
|
342
|
+
}
|
|
204
343
|
const declared = SEED_COLLECTION_PURPOSE[collection];
|
|
205
344
|
return isSeedPurpose(declared) ? declared : "test";
|
|
206
345
|
}
|
|
@@ -228,8 +367,20 @@ export function seedPurposeFields(purpose) {
|
|
|
228
367
|
* Returns a NEW object; the seed arrays are module-level constants re-imported
|
|
229
368
|
* on every CLI invocation and must not be mutated.
|
|
230
369
|
*/
|
|
231
|
-
export function stampSeedPurpose(collection, row
|
|
232
|
-
|
|
370
|
+
export function stampSeedPurpose(collection, row,
|
|
371
|
+
/** Pass this whenever the caller holds the id and the row does not — see
|
|
372
|
+
* `resolveSeedPurpose`'s `docId` for why `loadGeneric` must. */
|
|
373
|
+
docId) {
|
|
374
|
+
/*
|
|
375
|
+
* The cast is to `Record<string, unknown>` only, which is a WIDENING — every
|
|
376
|
+
* object satisfies it structurally, and `resolveSeedPurpose` reads `id`,
|
|
377
|
+
* `uid` and the derived `field` with `typeof` guards rather than trusting the
|
|
378
|
+
* type. 🛑 Deliberately NOT `as any`: that would silence the `collection`
|
|
379
|
+
* argument's check too, which is the mistake the plan records at the
|
|
380
|
+
* `sendNotification`/`as never` sites.
|
|
381
|
+
*/
|
|
382
|
+
const purpose = resolveSeedPurpose(collection, row, docId);
|
|
383
|
+
return { ...row, ...seedPurposeFields(purpose) };
|
|
233
384
|
}
|
|
234
385
|
/**
|
|
235
386
|
* Mark a whole array as something other than its collection default — the
|
package/dist/seo/json-ld.d.ts
CHANGED
|
@@ -93,7 +93,16 @@ export interface FaqJsonLdInput {
|
|
|
93
93
|
}
|
|
94
94
|
export interface BreadcrumbJsonLdItem {
|
|
95
95
|
name: string;
|
|
96
|
-
|
|
96
|
+
/**
|
|
97
|
+
* Absent on the LAST crumb — the page you are already on.
|
|
98
|
+
*
|
|
99
|
+
* 🛑 This was required, and `url.startsWith(...)` threw on a missing one. It
|
|
100
|
+
* is optional because schema.org's own breadcrumb guidance is that the final
|
|
101
|
+
* element identifies the current page and its `item` may be omitted; a
|
|
102
|
+
* self-link there is accepted but says nothing. Every existing caller passes
|
|
103
|
+
* a url, so widening it breaks none of them.
|
|
104
|
+
*/
|
|
105
|
+
url?: string;
|
|
97
106
|
}
|
|
98
107
|
export interface BlogPostJsonLdInput {
|
|
99
108
|
title: string;
|
package/dist/seo/json-ld.js
CHANGED
|
@@ -174,7 +174,15 @@ export function breadcrumbJsonLd(items) {
|
|
|
174
174
|
"@type": "ListItem",
|
|
175
175
|
position: index + 1,
|
|
176
176
|
name: item.name,
|
|
177
|
-
|
|
177
|
+
/*
|
|
178
|
+
* `item` is OMITTED rather than emitted empty when there is no url.
|
|
179
|
+
* `item: ""` would resolve to the site root, so every page's final crumb
|
|
180
|
+
* would link to the homepage — a wrong link is worse than an absent one,
|
|
181
|
+
* and absent is what the spec asks for on the current page.
|
|
182
|
+
*/
|
|
183
|
+
...(item.url
|
|
184
|
+
? { item: item.url.startsWith("http") ? item.url : `${SITE_URL}${item.url}` }
|
|
185
|
+
: {}),
|
|
178
186
|
})),
|
|
179
187
|
};
|
|
180
188
|
}
|
package/dist/server.d.ts
CHANGED
|
@@ -1,4 +1,11 @@
|
|
|
1
1
|
export { isAuthError } from "./utils/auth-error";
|
|
2
|
+
export { searchEverything } from "./_internal/server/features/search/search-everything";
|
|
3
|
+
export type { SearchHit, SearchGroup, SearchSource, SearchEverythingResult, } from "./_internal/server/features/search/search-everything";
|
|
4
|
+
export { SEARCH_SOURCES } from "./_internal/server/features/search/search-sources";
|
|
5
|
+
export { getPartByKey, getModelsUsingPart, getCompositionById, getPartsForComposition, getCompositionsForCategory, listPartsBySlot, } from "./_internal/server/features/parts/data";
|
|
6
|
+
export { renderPartOg, renderPartClassificationOg, } from "./_internal/server/features/parts/og";
|
|
7
|
+
export { assembleFromComponents, partOutToComponents, planAssembly, assertAssemblyInput, MAX_ASSEMBLY_COMPONENTS, } from "./_internal/server/features/parts/assembly";
|
|
8
|
+
export type { AssemblyComponent, AssembleInput, PartOutInput, AssemblyResult, AssemblyPlan, StockWrite, } from "./_internal/server/features/parts/assembly";
|
|
2
9
|
export { safeRead, installDegradedReadReporter, } from "./errors/safe-read";
|
|
3
10
|
export type { DegradedReadReport } from "./errors/safe-read";
|
|
4
11
|
export { ServerErrorsRepository, serverErrorsRepository, } from "./features/server-errors/repository/server-errors.repository";
|
package/dist/server.js
CHANGED
|
@@ -2,6 +2,17 @@
|
|
|
2
2
|
// isAuthError - Pure util — detects auth errors from caught exceptions (usable in server actions).
|
|
3
3
|
export { isAuthError } from "./utils/auth-error.js";
|
|
4
4
|
// [SERVER-ONLY] safeRead — wraps optional reads whose failure must not break a page.
|
|
5
|
+
// D2 Fix 4 — cross-resource search. Exported from `server.ts` because it takes
|
|
6
|
+
// a Firestore handle and runs only server-side; `_internal/` is not an
|
|
7
|
+
// exported subpath, and a consumer page must not reach into it.
|
|
8
|
+
export { searchEverything } from "./_internal/server/features/search/search-everything.js";
|
|
9
|
+
export { SEARCH_SOURCES } from "./_internal/server/features/search/search-sources.js";
|
|
10
|
+
// D12 — part + composition reads. Every one is by document id or ONE
|
|
11
|
+
// indexed equality; none needs a composite index.
|
|
12
|
+
export { getPartByKey, getModelsUsingPart, getCompositionById, getPartsForComposition, getCompositionsForCategory, listPartsBySlot, } from "./_internal/server/features/parts/data.js";
|
|
13
|
+
export { renderPartOg, renderPartClassificationOg, } from "./_internal/server/features/parts/og.js";
|
|
14
|
+
// D13 — assembly and part-out. Both run in a real transaction that RE-READS.
|
|
15
|
+
export { assembleFromComponents, partOutToComponents, planAssembly, assertAssemblyInput, MAX_ASSEMBLY_COMPONENTS, } from "./_internal/server/features/parts/assembly.js";
|
|
5
16
|
// Persists DEGRADED_READ rows via the installed reporter; returns typed fallback on error.
|
|
6
17
|
export { safeRead, installDegradedReadReporter, } from "./errors/safe-read.js";
|
|
7
18
|
// [SERVER-ONLY] serverErrorsRepository — Firestore-backed persistent log for 5xx + selected 4xx.
|