@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.
Files changed (220) hide show
  1. package/dist/_internal/server/features/categories/adapters.d.ts +47 -0
  2. package/dist/_internal/server/features/categories/adapters.js +235 -0
  3. package/dist/_internal/server/features/checkout/actions.js +161 -22
  4. package/dist/_internal/server/features/checkout/expand-order-items.js +34 -0
  5. package/dist/_internal/server/features/orders/adapters.js +21 -0
  6. package/dist/_internal/server/features/refunds/actions.js +1 -1
  7. package/dist/_internal/server/features/seo/sitemap.js +54 -15
  8. package/dist/_internal/server/features/stores/adapters.d.ts +1 -1
  9. package/dist/_internal/server/features/stores/adapters.js +6 -0
  10. package/dist/_internal/server/functions/scheduled.d.ts +4 -2
  11. package/dist/_internal/server/functions/scheduled.js +64 -6
  12. package/dist/_internal/server/jobs/core/adminAnalytics.d.ts +0 -1
  13. package/dist/_internal/server/jobs/core/adminAnalytics.js +9 -1
  14. package/dist/_internal/server/jobs/core/analyticsAlertEvaluate.d.ts +47 -0
  15. package/dist/_internal/server/jobs/core/analyticsAlertEvaluate.js +468 -0
  16. package/dist/_internal/server/jobs/core/autoPayoutEligibility.js +14 -1
  17. package/dist/_internal/server/jobs/core/countersReconcile.js +24 -8
  18. package/dist/_internal/server/jobs/core/onCategoryWrite.js +54 -0
  19. package/dist/_internal/server/jobs/core/priceIndexRollup.d.ts +47 -0
  20. package/dist/_internal/server/jobs/core/priceIndexRollup.js +234 -0
  21. package/dist/_internal/server/jobs/core/{testerSandboxCleanup.d.ts → testDataCleanup.d.ts} +3 -3
  22. package/dist/_internal/server/jobs/core/{testerSandboxCleanup.js → testDataCleanup.js} +3 -3
  23. package/dist/_internal/server/jobs/core/weeklyPayoutEligibility.js +5 -1
  24. package/dist/_internal/server/jobs/handlers/analyticsAlertEvaluate.d.ts +2 -0
  25. package/dist/_internal/server/jobs/handlers/analyticsAlertEvaluate.js +2 -0
  26. package/dist/_internal/server/jobs/handlers/index.d.ts +3 -1
  27. package/dist/_internal/server/jobs/handlers/index.js +3 -1
  28. package/dist/_internal/server/jobs/handlers/messages.d.ts +5 -2
  29. package/dist/_internal/server/jobs/handlers/messages.js +5 -2
  30. package/dist/_internal/server/jobs/handlers/priceIndexRollup.d.ts +2 -0
  31. package/dist/_internal/server/jobs/handlers/priceIndexRollup.js +2 -0
  32. package/dist/_internal/server/jobs/handlers/testDataCleanup.d.ts +2 -0
  33. package/dist/_internal/server/jobs/handlers/testDataCleanup.js +2 -0
  34. package/dist/_internal/shared/actions/action-registry.js +34 -0
  35. package/dist/_internal/shared/checkout/order-math.d.ts +113 -3
  36. package/dist/_internal/shared/checkout/order-math.js +194 -7
  37. package/dist/_internal/shared/features/categories/price-guidance.d.ts +110 -0
  38. package/dist/_internal/shared/features/categories/price-guidance.js +136 -0
  39. package/dist/_internal/shared/features/site-settings/gst-registration.d.ts +90 -0
  40. package/dist/_internal/shared/features/site-settings/gst-registration.js +126 -0
  41. package/dist/_internal/shared/fees/calculator.d.ts +105 -0
  42. package/dist/_internal/shared/fees/calculator.js +143 -0
  43. package/dist/_internal/shared/listing-types/art/config.d.ts +0 -6
  44. package/dist/_internal/shared/listing-types/classified/config.d.ts +0 -6
  45. package/dist/_internal/shared/listing-types/digital-code/config.d.ts +0 -6
  46. package/dist/_internal/shared/listing-types/live/config.d.ts +0 -6
  47. package/dist/_internal/shared/listing-types/standard/config.d.ts +0 -6
  48. package/dist/_internal/shared/listing-types/stickers/config.d.ts +0 -6
  49. package/dist/_internal/shared/templating/placeholders.d.ts +70 -0
  50. package/dist/_internal/shared/templating/placeholders.js +73 -0
  51. package/dist/client.d.ts +2 -0
  52. package/dist/client.js +1 -0
  53. package/dist/constants/api-endpoints.d.ts +42 -0
  54. package/dist/constants/api-endpoints.js +14 -0
  55. package/dist/features/admin/actions/admin-actions.d.ts +1 -1
  56. package/dist/features/admin/actions/admin-actions.js +1 -1
  57. package/dist/features/admin/components/AdminCategoryEditorView.js +269 -3
  58. package/dist/features/admin/components/AdminProductEditorView.js +8 -1
  59. package/dist/features/admin/components/AdminProductsView.js +78 -7
  60. package/dist/features/admin/components/AdminSiteSettingsView.js +20 -1
  61. package/dist/features/admin/components/AdminTaxCodeEditorView.d.ts +10 -0
  62. package/dist/features/admin/components/AdminTaxCodeEditorView.js +160 -0
  63. package/dist/features/admin/components/AdminTaxCodesView.d.ts +13 -0
  64. package/dist/features/admin/components/AdminTaxCodesView.js +86 -0
  65. package/dist/features/admin/components/AdminTesterChecklistView.js +1 -1
  66. package/dist/features/admin/components/analytics/AdminTopProductsTable.js +1 -1
  67. package/dist/features/auth/schemas/firestore.d.ts +0 -1
  68. package/dist/features/blog/schemas/firestore.d.ts +2 -2
  69. package/dist/features/catalogue/components/CatalogueItemEditorView.js +14 -3
  70. package/dist/features/categories/api/[id]/description-template/route.d.ts +45 -0
  71. package/dist/features/categories/api/[id]/description-template/route.js +252 -0
  72. package/dist/features/categories/api/route.js +156 -11
  73. package/dist/features/categories/components/CategoryDetailPageView.js +43 -10
  74. package/dist/features/categories/components/CategoryTemplatePicker.d.ts +65 -0
  75. package/dist/features/categories/components/CategoryTemplatePicker.js +170 -0
  76. package/dist/features/categories/constants/listing-categories.d.ts +17 -0
  77. package/dist/features/categories/constants/listing-categories.js +53 -0
  78. package/dist/features/categories/hooks/useCategoryTree.js +28 -2
  79. package/dist/features/categories/repository/categories.repository.d.ts +62 -11
  80. package/dist/features/categories/repository/categories.repository.js +178 -35
  81. package/dist/features/categories/repository/price-index.repository.d.ts +115 -0
  82. package/dist/features/categories/repository/price-index.repository.js +85 -0
  83. package/dist/features/categories/schemas/category-content.d.ts +206 -0
  84. package/dist/features/categories/schemas/category-content.js +28 -0
  85. package/dist/features/categories/schemas/firestore.d.ts +74 -5
  86. package/dist/features/categories/schemas/firestore.js +74 -3
  87. package/dist/features/categories/schemas/index.d.ts +1047 -0
  88. package/dist/features/categories/schemas/index.js +96 -0
  89. package/dist/features/categories/server.d.ts +1 -0
  90. package/dist/features/categories/server.js +7 -0
  91. package/dist/features/categories/types/index.d.ts +9 -0
  92. package/dist/features/events/schemas/firestore.d.ts +2 -2
  93. package/dist/features/faq/schemas/firestore.d.ts +2 -1
  94. package/dist/features/faq/schemas/firestore.js +20 -8
  95. package/dist/features/orders/schemas/firestore.d.ts +70 -0
  96. package/dist/features/orders/types/index.d.ts +55 -0
  97. package/dist/features/products/columns/index.d.ts +0 -1
  98. package/dist/features/products/columns/index.js +5 -1
  99. package/dist/features/products/components/ProductFilters.d.ts +0 -9
  100. package/dist/features/products/components/ProductForm.d.ts +27 -1
  101. package/dist/features/products/components/ProductForm.js +26 -12
  102. package/dist/features/products/constants/sieve.d.ts +0 -6
  103. package/dist/features/products/constants/sieve.js +35 -2
  104. package/dist/features/products/repository/products.repository.d.ts +115 -1
  105. package/dist/features/products/repository/products.repository.js +491 -39
  106. package/dist/features/products/schemas/firestore.d.ts +65 -5
  107. package/dist/features/products/schemas/firestore.js +9 -0
  108. package/dist/features/products/schemas/product-features.d.ts +140 -2
  109. package/dist/features/products/schemas/product-features.js +62 -1
  110. package/dist/features/promotions/repository/coupons.repository.js +6 -1
  111. package/dist/features/promotions/schemas/firestore.d.ts +44 -1
  112. package/dist/features/promotions/schemas/firestore.js +76 -2
  113. package/dist/features/seller/actions/seller-actions.js +17 -2
  114. package/dist/features/seller/components/PrintCenterView.d.ts +3 -2
  115. package/dist/features/seller/components/PrintCenterView.js +3 -2
  116. package/dist/features/seller/components/SellerAnalyticsAlertsView.js +21 -11
  117. package/dist/features/seller/components/SellerOrdersView.js +6 -1
  118. package/dist/features/seller/components/SellerPayoutSettingsView.js +19 -3
  119. package/dist/features/seller/components/SellerProductShell.d.ts +50 -2
  120. package/dist/features/seller/components/SellerProductShell.js +101 -9
  121. package/dist/features/seller/components/SellerProductsFilterDrawer.d.ts +13 -1
  122. package/dist/features/seller/components/SellerProductsFilterDrawer.js +2 -2
  123. package/dist/features/seller/components/SellerProductsView.js +108 -3
  124. package/dist/features/seller/types/index.d.ts +17 -0
  125. package/dist/features/seller/utils/product-draft-mapping.d.ts +13 -0
  126. package/dist/features/seller/utils/product-draft-mapping.js +17 -0
  127. package/dist/features/store-extensions/constants/alert-metrics.d.ts +86 -0
  128. package/dist/features/store-extensions/constants/alert-metrics.js +142 -0
  129. package/dist/features/store-extensions/schemas/firestore.d.ts +19 -1
  130. package/dist/features/stores/schemas/firestore.d.ts +31 -2
  131. package/dist/features/tax-codes/repository/tax-codes.repository.d.ts +55 -0
  132. package/dist/features/tax-codes/repository/tax-codes.repository.js +131 -0
  133. package/dist/features/tax-codes/schemas/firestore.d.ts +125 -0
  134. package/dist/features/tax-codes/schemas/firestore.js +82 -0
  135. package/dist/features/tax-codes/schemas/tax-code-form.d.ts +63 -0
  136. package/dist/features/tax-codes/schemas/tax-code-form.js +152 -0
  137. package/dist/features/tax-codes/schemas/tax-codes.validators.d.ts +107 -0
  138. package/dist/features/tax-codes/schemas/tax-codes.validators.js +85 -0
  139. package/dist/features/tester/actions/index.d.ts +0 -1
  140. package/dist/features/tester/actions/index.js +0 -1
  141. package/dist/features/tester/components/AdminTesterFeedbackIssuesView.js +1 -1
  142. package/dist/features/tester/components/AdminTesterFeedbackListView.js +1 -1
  143. package/dist/features/tester/components/index.d.ts +0 -4
  144. package/dist/features/tester/components/index.js +0 -2
  145. package/dist/features/tester/repository/tester-checklist-item.repository.d.ts +1 -20
  146. package/dist/features/tester/repository/tester-checklist-item.repository.js +0 -52
  147. package/dist/features/tester/schemas/firestore.d.ts +0 -6
  148. package/dist/features/tester/seed-data/authored/admin__admin-detail-round-trips.js +3 -3
  149. package/dist/features/tester/seed-data/authored/admin__site-system.js +4 -5
  150. package/dist/features/tester/seed-data/authored/content-discovery__category-counts-and-rollup.js +2 -2
  151. package/dist/features/tester/seed-data/authored/index.js +0 -2
  152. package/dist/features/tester/seed-data/authored/page-wiring__reachability.js +0 -19
  153. package/dist/features/tester/seed-data/tester-checklist-seed-data.js +8 -27
  154. package/dist/index.d.ts +21 -7
  155. package/dist/index.js +53 -7
  156. package/dist/next/routing/route-map.d.ts +2 -4
  157. package/dist/next/routing/route-map.js +1 -2
  158. package/dist/repositories/index.d.ts +1 -0
  159. package/dist/repositories/index.js +1 -0
  160. package/dist/schemas/registry.d.ts +506 -0
  161. package/dist/seed/_helpers/brand-rows.d.ts +35 -0
  162. package/dist/seed/_helpers/brand-rows.js +576 -0
  163. package/dist/seed/_helpers/category-content.d.ts +39 -0
  164. package/dist/seed/_helpers/category-content.js +634 -0
  165. package/dist/seed/_helpers/category-forest.d.ts +2 -0
  166. package/dist/seed/_helpers/category-forest.js +585 -243
  167. package/dist/seed/_helpers/category-models.d.ts +5 -0
  168. package/dist/seed/_helpers/category-models.js +1364 -0
  169. package/dist/seed/_helpers/category-tree.js +17 -1
  170. package/dist/seed/_helpers/character-features.d.ts +10 -0
  171. package/dist/seed/_helpers/character-features.js +118 -0
  172. package/dist/seed/_helpers/search-txt-wrappers.d.ts +10 -0
  173. package/dist/seed/_helpers/search-txt-wrappers.js +12 -1
  174. package/dist/seed/categories-seed-data.js +147 -169
  175. package/dist/seed/index.d.ts +1 -0
  176. package/dist/seed/index.js +1 -0
  177. package/dist/seed/manifest.d.ts +1 -1
  178. package/dist/seed/manifest.js +5 -0
  179. package/dist/seed/product-features-seed-data.d.ts +1 -8
  180. package/dist/seed/product-features-seed-data.js +358 -154
  181. package/dist/seed/products-art-seed-data.js +16 -4
  182. package/dist/seed/products-auctions-seed-data.js +6 -6
  183. package/dist/seed/products-classifieds-seed-data.js +17 -7
  184. package/dist/seed/products-digital-codes-seed-data.js +12 -12
  185. package/dist/seed/products-preorders-seed-data.js +8 -8
  186. package/dist/seed/products-prize-draws-seed-data.js +25 -8
  187. package/dist/seed/products-standard-seed-data.js +10 -60
  188. package/dist/seed/products-stickers-seed-data.js +6 -6
  189. package/dist/seed/site-settings-seed-data.js +24 -0
  190. package/dist/seed/store-extensions-seed-data.js +10 -4
  191. package/dist/seed/tax-codes-seed-data.d.ts +34 -0
  192. package/dist/seed/tax-codes-seed-data.js +100 -0
  193. package/dist/seed/test-data-ttl.d.ts +29 -0
  194. package/dist/seed/test-data-ttl.js +31 -0
  195. package/dist/server-entry.d.ts +1 -1
  196. package/dist/server-entry.js +6 -1
  197. package/dist/server.d.ts +2 -1
  198. package/dist/server.js +5 -2
  199. package/dist/styles.css +40 -0
  200. package/dist/ui/components/PaginatedSelect.d.ts +14 -0
  201. package/dist/ui/components/PaginatedSelect.js +0 -0
  202. package/dist/ui/components/PaginatedSelect.style.css +40 -0
  203. package/dist/utils/physical-location.d.ts +23 -0
  204. package/dist/utils/physical-location.js +52 -0
  205. package/dist/utils/search-txt-builders.d.ts +20 -0
  206. package/dist/utils/search-txt-builders.js +36 -0
  207. package/package.json +1 -1
  208. package/scripts/seed-cli.mjs +4 -1
  209. package/dist/_internal/server/jobs/handlers/testerSandboxCleanup.d.ts +0 -2
  210. package/dist/_internal/server/jobs/handlers/testerSandboxCleanup.js +0 -2
  211. package/dist/features/tester/actions/leaderboard-actions.d.ts +0 -3
  212. package/dist/features/tester/actions/leaderboard-actions.js +0 -5
  213. package/dist/features/tester/components/BugHunterLeaderboardView.d.ts +0 -23
  214. package/dist/features/tester/components/BugHunterLeaderboardView.js +0 -18
  215. package/dist/features/tester/components/TesterHubView.d.ts +0 -5
  216. package/dist/features/tester/components/TesterHubView.js +0 -110
  217. package/dist/features/tester/seed-data/authored/public-pages__bug-hunters.d.ts +0 -2
  218. package/dist/features/tester/seed-data/authored/public-pages__bug-hunters.js +0 -78
  219. package/dist/features/tester/seed-data/tester-ttl.d.ts +0 -4
  220. package/dist/features/tester/seed-data/tester-ttl.js +0 -6
@@ -1,10 +1,12 @@
1
1
  import { normalizeError } from "../../../../errors/normalize";
2
2
  import { getAdminDb } from "../../../../providers/db-firebase";
3
3
  import { ROUTES } from "../../../../next/routing/route-map";
4
- import { PRODUCT_COLLECTION } from "../../../..";
4
+ import { PRODUCT_COLLECTION } from "../../../../features/products/schemas/firestore";
5
5
  import { EVENTS_COLLECTION, EVENT_FIELDS } from "../../../../features/events";
6
6
  import { BLOG_POSTS_COLLECTION, BLOG_POST_FIELDS } from "../../../../features/blog";
7
7
  import { CATEGORIES_COLLECTION, CATEGORY_FIELDS } from "../../../../features/categories";
8
+ // Imported from the DEFINING module, not the feature barrel (Root Cause #18).
9
+ import { isListingCategory } from "../../../../features/categories/constants/listing-categories";
8
10
  import { STORE_COLLECTION, STORE_FIELDS } from "../../../../features/stores";
9
11
  import { SCAMMER_COLLECTION } from "../../../../features/scams/schemas/firestore";
10
12
  import { serverLogger } from "../../../../monitoring/server-logger";
@@ -16,7 +18,7 @@ const PRODUCT_SLUG = "slug";
16
18
  const PRODUCT_UPDATED_AT = "updatedAt";
17
19
  /**
18
20
  * Tester-sandbox fixtures must never reach the public sitemap. They are created
19
- * and destroyed on a cycle by `testerSandboxCleanup` and by each tester run's
21
+ * and destroyed on a cycle by `testDataCleanup` and by each tester run's
20
22
  * wipe/seed, so anything Google indexes from them 404s shortly after. 34 of 182 sitemap
21
23
  * URLs were disposable test fixtures before this filter existed.
22
24
  *
@@ -58,6 +60,47 @@ function sitemapSectionFailed(label, err) {
58
60
  serverLogger.error(`sitemap: section "${label}" failed and returned 0 URLs`, { section: label, error: err });
59
61
  return [];
60
62
  }
63
+ /**
64
+ * Read every document a query matches, in pages.
65
+ *
66
+ * 🛑 WHY: three category fetchers below carried a bare `.limit(500)` and then
67
+ * filtered in memory, so past 500 active rows the sitemap emitted an
68
+ * **unpredictable subset** — and `scripts/deploy.mjs` only asserts a section is
69
+ * non-ZERO, so truncation passes the deploy gate silently. The taxonomy this
70
+ * plan seeds takes `categories` from 58 to ~330 rows, which is still under 500
71
+ * today; the point is that crossing it would be invisible.
72
+ *
73
+ * `MAX_SITEMAP_DOCS` is a real ceiling, kept well under the 50,000-URL
74
+ * sitemap-file limit, and crossing it is LOGGED rather than swallowed — the
75
+ * opposite of a bare `.limit()`, which cannot tell you it truncated.
76
+ *
77
+ * An explicit `orderBy("__name__")` is required: `startAfter(docSnapshot)`
78
+ * needs a defined sort to page against, and a `where`-only query's implicit
79
+ * document order is not something to rely on across pages.
80
+ */
81
+ const SITEMAP_PAGE_SIZE = 500;
82
+ const MAX_SITEMAP_DOCS = 10000;
83
+ async function readAllDocs(query, label) {
84
+ const out = [];
85
+ let cursor = null;
86
+ for (;;) {
87
+ let page = query.orderBy("__name__").limit(SITEMAP_PAGE_SIZE);
88
+ if (cursor)
89
+ page = page.startAfter(cursor);
90
+ const snap = await page.get();
91
+ if (snap.empty)
92
+ break;
93
+ out.push(...snap.docs);
94
+ if (out.length >= MAX_SITEMAP_DOCS) {
95
+ serverLogger.error(`sitemap: section "${label}" hit the ${MAX_SITEMAP_DOCS}-document ceiling and is TRUNCATED`, { section: label, scanned: out.length });
96
+ return out.slice(0, MAX_SITEMAP_DOCS);
97
+ }
98
+ if (snap.size < SITEMAP_PAGE_SIZE)
99
+ break;
100
+ cursor = snap.docs[snap.docs.length - 1];
101
+ }
102
+ return out;
103
+ }
61
104
  function staticPages(baseUrl) {
62
105
  const page = (path, changeFreq, priority) => ({ url: `${baseUrl}${path}`, lastModified: new Date(), changeFrequency: changeFreq, priority });
63
106
  return [
@@ -250,14 +293,12 @@ async function fetchEventUrls(baseUrl) {
250
293
  async function fetchCategoryTypeUrls(baseUrl, categoryType, buildUrl, label) {
251
294
  try {
252
295
  const db = getAdminDb();
253
- const snap = await db
296
+ const docs = await readAllDocs(db
254
297
  .collection(CATEGORIES_COLLECTION)
255
298
  .where(CATEGORY_FIELDS.CATEGORY_TYPE, "==", categoryType)
256
299
  .where(CATEGORY_FIELDS.IS_ACTIVE, "==", true)
257
- .select(CATEGORY_FIELDS.SLUG, CATEGORY_FIELDS.UPDATED_AT, TEST_DATA_FIELD)
258
- .limit(500)
259
- .get();
260
- return snap.docs
300
+ .select(CATEGORY_FIELDS.SLUG, CATEGORY_FIELDS.UPDATED_AT, TEST_DATA_FIELD), label);
301
+ return docs
261
302
  .filter((doc) => !isTestDoc(doc.data()))
262
303
  .map((doc) => {
263
304
  const data = doc.data();
@@ -289,23 +330,21 @@ async function fetchCategoryTypeUrls(baseUrl, categoryType, buildUrl, label) {
289
330
  * stay in the `.select()` list or it comes back undefined and nothing is
290
331
  * rejected.
291
332
  */
292
- const NON_LISTING_CATEGORY_TYPES = new Set(["brand", "bundle", "sublisting"]);
293
333
  async function fetchCategoryUrls(baseUrl) {
294
334
  try {
295
335
  const db = getAdminDb();
296
- const snap = await db
336
+ const docs = await readAllDocs(db
297
337
  .collection(CATEGORIES_COLLECTION)
298
338
  .where(CATEGORY_FIELDS.IS_ACTIVE, "==", true)
299
- .select(CATEGORY_FIELDS.SLUG, CATEGORY_FIELDS.UPDATED_AT, CATEGORY_FIELDS.CATEGORY_TYPE, TEST_DATA_FIELD)
300
- .limit(500)
301
- .get();
302
- return snap.docs
339
+ .select(CATEGORY_FIELDS.SLUG, CATEGORY_FIELDS.UPDATED_AT, CATEGORY_FIELDS.CATEGORY_TYPE, TEST_DATA_FIELD), "category");
340
+ return docs
303
341
  .filter((doc) => {
304
342
  const data = doc.data();
305
343
  if (isTestDoc(data))
306
344
  return false;
307
- const kind = data[CATEGORY_FIELDS.CATEGORY_TYPE];
308
- return typeof kind !== "string" || !NON_LISTING_CATEGORY_TYPES.has(kind);
345
+ return isListingCategory({
346
+ categoryType: data[CATEGORY_FIELDS.CATEGORY_TYPE],
347
+ });
309
348
  })
310
349
  .map((doc) => {
311
350
  const data = doc.data();
@@ -27,7 +27,7 @@ import type { StoreDetail, StoreListItem } from "../../../../features/stores/typ
27
27
  */
28
28
  export declare const PUBLIC_STORE_FIELDS: readonly ["id", "storeSlug", "ownerId", "storeName", "storeDescription", "storeCategory", "storeLogoURL", "storeBannerURL", "status", "isPublic", "stats", "bio", "location", "website", "socialLinks", "returnPolicy", "shippingPolicy", "isVacationMode", "vacationMessage", "createdAt"];
29
29
  /** Everything else, with the reason it stays server-side. */
30
- export declare const PRIVATE_STORE_FIELDS: readonly ["searchTxt", "updatedAt", "whatsappConfig", "adminNotes", "statusHistory", "statusHistoryTruncated", "isVerified", "suspensionReason", "capabilities", "customCommissionRate", "googleReviews", "shippingConfig", "emiEnabled", "isFeatured", "vacationReturnDate", "isTestData", "testDataExpiresAt"];
30
+ export declare const PRIVATE_STORE_FIELDS: readonly ["searchTxt", "updatedAt", "whatsappConfig", "adminNotes", "statusHistory", "statusHistoryTruncated", "isVerified", "suspensionReason", "capabilities", "customCommissionRate", "googleReviews", "shippingConfig", "emiEnabled", "pricesIncludeGst", "isFeatured", "vacationReturnDate", "isTestData", "testDataExpiresAt"];
31
31
  /**
32
32
  * Accepted input: a `StoreDocument` (the common case) or a loosely-typed
33
33
  * Firestore row. The legacy top-level counters below predate `stats` and are
@@ -46,6 +46,12 @@ export const PRIVATE_STORE_FIELDS = [
46
46
  "googleReviews", // placeId is billed-API config; the RSC reads it server-side
47
47
  "shippingConfig", // resolved server-side into per-item shipping at cart time
48
48
  "emiEnabled", // read server-side by the checkout/product RSCs, never from a public payload
49
+ // A seller AUTHORING preference with no public reader by construction: the
50
+ // stored `price` is always tax-inclusive, so a buyer never needs to know how
51
+ // the seller typed it. Read only by `ProductRepository` at write time, and
52
+ // publishing it would invite a client to "correct" a price that is already
53
+ // correct.
54
+ "pricesIncludeGst",
49
55
  "isFeatured", // ranking input; not part of the public store profile
50
56
  "vacationReturnDate", // not rendered by StoreAboutView; add to PUBLIC when a reader exists
51
57
  "isTestData", // tester-sandbox tagging; filtered by filterTestDataForViewer
@@ -7,6 +7,8 @@ export declare const couponExpiry: import("./types").ScheduledFunctionDefinition
7
7
  export declare const offerExpiry: import("./types").ScheduledFunctionDefinition;
8
8
  export declare const productStatsSync: import("./types").ScheduledFunctionDefinition;
9
9
  export declare const revenueRollup: import("./types").ScheduledFunctionDefinition;
10
+ export declare const priceIndexRollup: import("./types").ScheduledFunctionDefinition;
11
+ export declare const analyticsAlertEvaluate: import("./types").ScheduledFunctionDefinition;
10
12
  export declare const pageViewRollup: import("./types").ScheduledFunctionDefinition;
11
13
  export declare const pageViewPrune: import("./types").ScheduledFunctionDefinition;
12
14
  export declare const dailyStatusDigest: import("./types").ScheduledFunctionDefinition;
@@ -20,9 +22,9 @@ export declare const autoPayoutEligibility: import("./types").ScheduledFunctionD
20
22
  export declare const cleanupRtdbEvents: import("./types").ScheduledFunctionDefinition;
21
23
  export declare const mediaTmpCleanup: import("./types").ScheduledFunctionDefinition;
22
24
  export declare const draftPrune: import("./types").ScheduledFunctionDefinition;
23
- export declare const testerSandboxCleanup: import("./types").ScheduledFunctionDefinition;
25
+ export declare const testDataCleanup: import("./types").ScheduledFunctionDefinition;
24
26
  export declare const prizeDrawExpiryReveal: import("./types").ScheduledFunctionDefinition;
25
27
  export declare const bundleStockSync: import("./types").ScheduledFunctionDefinition;
26
28
  export declare const emiInstallmentReminder: import("./types").ScheduledFunctionDefinition;
27
29
  export declare const catalogueImageStalenessReminder: import("./types").ScheduledFunctionDefinition;
28
- export declare const SCHEDULED_FUNCTIONS: readonly [import("./types").ScheduledFunctionDefinition, import("./types").ScheduledFunctionDefinition, import("./types").ScheduledFunctionDefinition, import("./types").ScheduledFunctionDefinition, import("./types").ScheduledFunctionDefinition, import("./types").ScheduledFunctionDefinition, import("./types").ScheduledFunctionDefinition, import("./types").ScheduledFunctionDefinition, import("./types").ScheduledFunctionDefinition, import("./types").ScheduledFunctionDefinition, import("./types").ScheduledFunctionDefinition, import("./types").ScheduledFunctionDefinition, import("./types").ScheduledFunctionDefinition, import("./types").ScheduledFunctionDefinition, import("./types").ScheduledFunctionDefinition, import("./types").ScheduledFunctionDefinition, import("./types").ScheduledFunctionDefinition, import("./types").ScheduledFunctionDefinition, import("./types").ScheduledFunctionDefinition, import("./types").ScheduledFunctionDefinition, import("./types").ScheduledFunctionDefinition, import("./types").ScheduledFunctionDefinition, import("./types").ScheduledFunctionDefinition, import("./types").ScheduledFunctionDefinition, import("./types").ScheduledFunctionDefinition, import("./types").ScheduledFunctionDefinition, import("./types").ScheduledFunctionDefinition];
30
+ export declare const SCHEDULED_FUNCTIONS: readonly [import("./types").ScheduledFunctionDefinition, import("./types").ScheduledFunctionDefinition, import("./types").ScheduledFunctionDefinition, import("./types").ScheduledFunctionDefinition, import("./types").ScheduledFunctionDefinition, import("./types").ScheduledFunctionDefinition, import("./types").ScheduledFunctionDefinition, import("./types").ScheduledFunctionDefinition, import("./types").ScheduledFunctionDefinition, import("./types").ScheduledFunctionDefinition, import("./types").ScheduledFunctionDefinition, import("./types").ScheduledFunctionDefinition, import("./types").ScheduledFunctionDefinition, import("./types").ScheduledFunctionDefinition, import("./types").ScheduledFunctionDefinition, import("./types").ScheduledFunctionDefinition, import("./types").ScheduledFunctionDefinition, import("./types").ScheduledFunctionDefinition, import("./types").ScheduledFunctionDefinition, import("./types").ScheduledFunctionDefinition, import("./types").ScheduledFunctionDefinition, import("./types").ScheduledFunctionDefinition, import("./types").ScheduledFunctionDefinition, import("./types").ScheduledFunctionDefinition, import("./types").ScheduledFunctionDefinition, import("./types").ScheduledFunctionDefinition, import("./types").ScheduledFunctionDefinition, import("./types").ScheduledFunctionDefinition, import("./types").ScheduledFunctionDefinition];
@@ -1,6 +1,6 @@
1
1
  // Scheduled function definitions (Cloud Scheduler triggers).
2
2
  // Cron syntax accepts both Firebase shorthand and standard cron expressions.
3
- import { auctionSettlementHandler, autoPayoutEligibilityHandler, bundleStockSyncHandler, cartPruneHandler, cleanupRtdbEventsHandler, countersReconcileHandler, couponExpiryHandler, dailyDataCleanupHandler, draftPruneHandler, emiInstallmentReminderHandler, catalogueImageStalenessReminderHandler, mediaTmpCleanupHandler, notificationPruneHandler, offerExpiryHandler, paymentWindowTimeoutHandler, hardBanReinstatementHandler, paymentReviewAutoApproveHandler, pendingOrderTimeoutHandler, positionsReconcileHandler, prizeDrawExpiryRevealHandler, productStatsSyncHandler, revenueRollupHandler, pageViewRollupHandler, pageViewPruneHandler, dailyStatusDigestHandler, weeklyPayoutEligibilityHandler, testerSandboxCleanupHandler, } from "../jobs/handlers";
3
+ import { auctionSettlementHandler, autoPayoutEligibilityHandler, bundleStockSyncHandler, cartPruneHandler, cleanupRtdbEventsHandler, countersReconcileHandler, couponExpiryHandler, dailyDataCleanupHandler, draftPruneHandler, emiInstallmentReminderHandler, catalogueImageStalenessReminderHandler, mediaTmpCleanupHandler, notificationPruneHandler, offerExpiryHandler, paymentWindowTimeoutHandler, hardBanReinstatementHandler, paymentReviewAutoApproveHandler, pendingOrderTimeoutHandler, positionsReconcileHandler, prizeDrawExpiryRevealHandler, productStatsSyncHandler, revenueRollupHandler, priceIndexRollupHandler, analyticsAlertEvaluateHandler, pageViewRollupHandler, pageViewPruneHandler, dailyStatusDigestHandler, weeklyPayoutEligibilityHandler, testDataCleanupHandler, } from "../jobs/handlers";
4
4
  import { defineFunction } from "./define";
5
5
  const REGION = "asia-south1";
6
6
  const EVERY_5_MIN = "every 5 minutes";
@@ -67,6 +67,49 @@ export const revenueRollup = defineFunction({
67
67
  handler: revenueRollupHandler,
68
68
  options: { region: REGION, timeoutSeconds: 300, memory: "256MiB", maxInstances: 1 },
69
69
  });
70
+ /*
71
+ * The realised-price index — 04:30 UTC, i.e. **10:00 IST**, the cadence the
72
+ * page states as "prices as of".
73
+ *
74
+ * 🛑 Its OWN function rather than a pass inside `countersReconcile`, which
75
+ * already runs at its 540s ceiling. A separate job can fail without taking
76
+ * category metrics down with it, and the two share nothing beyond reading
77
+ * products.
78
+ *
79
+ * It writes `priceIndex/{leafId}` and never `categories` — see the
80
+ * repository's header for why that is not a preference. A job writing a price
81
+ * band onto the collection `onCategoryWrite` watches is Root Cause #92's exact
82
+ * shape (1,017,548 invocations in 24h), and `notifyRevalidate` would
83
+ * additionally drop each category page's ISR entry nightly on a project that
84
+ * was paused for exceeding ISR writes by 15.5×.
85
+ */
86
+ export const priceIndexRollup = defineFunction({
87
+ name: "priceIndexRollup",
88
+ description: "Roll up realised sale prices per leaf category into priceIndex/{leafId} (daily 04:30 UTC = 10:00 IST).",
89
+ trigger: { kind: "schedule", cron: "30 4 * * *", timeZone: "UTC" },
90
+ handler: priceIndexRollupHandler,
91
+ options: { region: REGION, timeoutSeconds: 540, memory: "512MiB", maxInstances: 1 },
92
+ });
93
+ /*
94
+ * Evaluate the alert rules sellers and admins configured — hourly, 20 past.
95
+ *
96
+ * 🛑 This feature had a schema, a CRUD UI, two API routes, four seeded
97
+ * fixtures and **no evaluator anywhere**: zero references across all 59 job
98
+ * files, with `lastTriggeredAt` declared and written nowhere. So every alert a
99
+ * seller configured could never fire.
100
+ *
101
+ * Hourly rather than daily, because an alert whose point is "tell me when
102
+ * nothing is selling" is useless a day late — and the job's own 24h
103
+ * re-notify cooldown is what stops hourly evaluation becoming hourly mail.
104
+ * 20 past the hour keeps it clear of the dense :00/:15 band.
105
+ */
106
+ export const analyticsAlertEvaluate = defineFunction({
107
+ name: "analyticsAlertEvaluate",
108
+ description: "Evaluate seller and admin analytics alerts against live metrics and notify on a crossing (hourly at :20).",
109
+ trigger: { kind: "schedule", cron: "20 * * * *", timeZone: "UTC" },
110
+ handler: analyticsAlertEvaluateHandler,
111
+ options: { region: REGION, timeoutSeconds: 300, memory: "256MiB", maxInstances: 1 },
112
+ });
70
113
  /*
71
114
  * The rollup runs BEFORE the prune, and both before the retention boundary
72
115
  * moves. `pageViews` had neither until 2026-08-31: it wrote one document per
@@ -168,11 +211,24 @@ export const draftPrune = defineFunction({
168
211
  handler: draftPruneHandler,
169
212
  options: { region: REGION, timeoutSeconds: 300, memory: "256MiB", maxInstances: 1 },
170
213
  });
171
- export const testerSandboxCleanup = defineFunction({
172
- name: "testerSandboxCleanup",
173
- description: "Delete expired tester QA sandbox test data (daily 05:00 UTC).",
214
+ /*
215
+ * 🛑 RENAMED from `testerSandboxCleanup` (2026-10-10, plan §7). The job never
216
+ * had anything to do with the tester HARNESS — it sweeps any document carrying
217
+ * `isTestData: true` past its `testDataExpiresAt`, which is a seed-data concern
218
+ * and outlives the human tester programme being retired in B2.
219
+ *
220
+ * 🛑 A Scheduler job rename is DELETE-AND-RECREATE on deploy, not a rename:
221
+ * `firebase deploy --only functions` creates `testDataCleanup` and leaves
222
+ * `testerSandboxCleanup` running until it is explicitly deleted. Two jobs
223
+ * sweeping the same documents is not redundancy — it is the two-owners race
224
+ * that got `testerSandboxRefresh` removed (see the note below). Delete the old
225
+ * function in the same deploy.
226
+ */
227
+ export const testDataCleanup = defineFunction({
228
+ name: "testDataCleanup",
229
+ description: "Delete expired isTestData documents past their TTL (daily 05:00 UTC).",
174
230
  trigger: { kind: "schedule", cron: "0 5 * * *", timeZone: "UTC" },
175
- handler: testerSandboxCleanupHandler,
231
+ handler: testDataCleanupHandler,
176
232
  options: { region: REGION, timeoutSeconds: 300, memory: "256MiB", maxInstances: 1 },
177
233
  });
178
234
  // testerSandboxRefresh (every 4h: revert fixtures to seed shape, prune extras) was
@@ -220,6 +276,8 @@ export const SCHEDULED_FUNCTIONS = [
220
276
  offerExpiry,
221
277
  productStatsSync,
222
278
  revenueRollup,
279
+ priceIndexRollup,
280
+ analyticsAlertEvaluate,
223
281
  pageViewRollup,
224
282
  pageViewPrune,
225
283
  dailyStatusDigest,
@@ -233,7 +291,7 @@ export const SCHEDULED_FUNCTIONS = [
233
291
  cleanupRtdbEvents,
234
292
  mediaTmpCleanup,
235
293
  draftPrune,
236
- testerSandboxCleanup,
294
+ testDataCleanup,
237
295
  prizeDrawExpiryReveal,
238
296
  bundleStockSync,
239
297
  emiInstallmentReminder,
@@ -23,7 +23,6 @@ export interface AdminAnalyticsResult {
23
23
  revenue: number;
24
24
  orders: number;
25
25
  mainImage: string;
26
- viewCount: number;
27
26
  }>;
28
27
  }
29
28
  export declare function runAdminAnalytics(_input: void, ctx: JobContext): Promise<AdminAnalyticsResult>;
@@ -82,7 +82,15 @@ export async function runAdminAnalytics(_input, ctx) {
82
82
  const topProductsWithImages = topProducts.map((p, i) => ({
83
83
  ...p,
84
84
  mainImage: topProductDocs[i]?.mainImage ?? "",
85
- viewCount: topProductDocs[i]?.viewCount ?? 0,
85
+ /*
86
+ * 🛑 `viewCount: … ?? 0` removed 2026-10-10. The `?? 0` was the active
87
+ * ingredient: `viewCount` is absent from every product document, and
88
+ * coercing that absence to `0` made it pass `AdminTopProductsTable`'s
89
+ * `!= null` guard, so the table rendered a permanent "0 views".
90
+ *
91
+ * Dropping the key leaves it `undefined`, which is the truth. Real counts
92
+ * come from the `pageViews` collection once it is rolled up.
93
+ */
86
94
  }));
87
95
  return {
88
96
  summary: {
@@ -0,0 +1,47 @@
1
+ /**
2
+ * Core: evaluate the alert rules sellers and admins have configured — C3b.
3
+ *
4
+ * ## 🛑 The defect this closes: there was no evaluator at all
5
+ *
6
+ * `analyticsAlerts` had a schema, a seller CRUD UI, two API routes and four
7
+ * seeded fixtures — and **zero references across all 59 job files and
8
+ * `functions/`**. `lastTriggeredAt` was declared and written nowhere. So every
9
+ * alert a seller configured, and every one the seed created, could never fire.
10
+ *
11
+ * CLAUDE.md overstates this feature for exactly that reason: the parts that
12
+ * exist look complete, and the one that was missing is the one nothing points
13
+ * at. It also hid the metric-vocabulary split that C3a closed — two disjoint
14
+ * lists of metric names cannot be noticed while nothing reads the field.
15
+ *
16
+ * ## The switch is EXHAUSTIVE over `AlertMetric`
17
+ *
18
+ * `satisfies never` on the default branch, so adding a vocabulary member
19
+ * without an implementation does not compile. That is the mechanism behind the
20
+ * vocabulary's rule — *a metric is offerable only if the evaluator computes
21
+ * it* — rather than a convention someone has to remember.
22
+ *
23
+ * ## Read cost, and why it is bounded
24
+ *
25
+ * Metrics are computed ONCE PER OWNER, not once per alert: a seller with four
26
+ * alerts on orders pays for one orders query. Each query is bounded by
27
+ * `QUERY_LIMIT` and every count is reported as saturated when it hits the cap,
28
+ * because a count that silently stopped at 500 is a threshold comparison
29
+ * against a number that is not the number.
30
+ *
31
+ * 🛑 It does NOT scan per-product for `low_stock`. `availableQuantity <= N` is
32
+ * an inequality, and pairing one with an arbitrary sort is what demands an
33
+ * index nobody declares (Root Cause #59) — so the window is bounded and the
34
+ * predicate runs in memory, the same asymmetry § Availability describes.
35
+ */
36
+ import type { AlertOperator, AnalyticsAlertDocument } from "../../../../features/store-extensions/schemas/firestore";
37
+ import type { JobContext } from "../runtime/types";
38
+ /** Compare, with the operator the alert carries. */
39
+ export declare function alertFires(value: number, operator: AlertOperator, threshold: number): boolean;
40
+ /**
41
+ * Whether enough time has passed to notify again.
42
+ *
43
+ * An alert that has NEVER fired always notifies — absence of
44
+ * `lastTriggeredAt` means "not yet", never "recently".
45
+ */
46
+ export declare function mayRenotify(alert: AnalyticsAlertDocument, now: Date): boolean;
47
+ export declare function runAnalyticsAlertEvaluate(ctx: JobContext): Promise<void>;