@canonry/canonry 7.94.0 → 7.96.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 (142) hide show
  1. package/assets/agent-workspace/skills/aero/references/ai-traffic-tracking.md +4 -1
  2. package/assets/agent-workspace/skills/canonry/references/ai-traffic-tracking.md +4 -1
  3. package/assets/agent-workspace/skills/canonry/references/canonry-cli.md +12 -4
  4. package/assets/assets/{AgentSetupPage-BlPXN_oH.js → AgentSetupPage-DBiPrrdJ.js} +4 -4
  5. package/assets/assets/{AuditHistoryPanel-N8Z4gng7.js → AuditHistoryPanel-DSnIs064.js} +1 -1
  6. package/assets/assets/{BacklinksPage-DSrgqlkj.js → BacklinksPage-tPiG0nEP.js} +1 -1
  7. package/assets/assets/{HistoryPage-v2jbUkle.js → HistoryPage-sxYHrfNk.js} +1 -1
  8. package/assets/assets/MeasurementPropertyPage-B8EbifR2.js +4 -0
  9. package/assets/assets/ProjectPage-C1_A7iGy.js +11 -0
  10. package/assets/assets/{RunsPage-CXZJDdnr.js → RunsPage-RE6nLBTv.js} +1 -1
  11. package/assets/assets/{SettingsPage-CeLvWs_T.js → SettingsPage-Dn93SUD1.js} +1 -1
  12. package/assets/assets/SiteHealthSection-BEBYoCNu.js +2 -0
  13. package/assets/assets/ToneBadge-DFlmWh2A.js +60 -0
  14. package/assets/assets/{TrafficPage-CEsmS5N1.js → TrafficPage-cNokjIJa.js} +1 -1
  15. package/assets/assets/{TrafficSourceDetailPage-D31g1FXV.js → TrafficSourceDetailPage-DFGbx0zs.js} +1 -1
  16. package/assets/assets/{account-context-CGIyok0O.js → account-context-RM43ek4w.js} +1 -1
  17. package/assets/assets/{agent-trust-copy-B-3akxrd.js → agent-trust-copy-CRZ_oe24.js} +1 -1
  18. package/assets/assets/{card-BMjN_ofC.js → card-GFeSoV1m.js} +1 -1
  19. package/assets/assets/{format-helpers-Blu0QNcd.js → format-helpers-4dA9HFgE.js} +1 -1
  20. package/assets/assets/index-CohgAQ9L.js +60 -0
  21. package/assets/assets/{query-client-DdwTc28X.js → query-client-B3Z7Vqgv.js} +1 -1
  22. package/assets/assets/react-query-HC0PcMhZ.js +1 -0
  23. package/assets/assets/{react-sigma_core.esm.min-5sP0Pb_F.js → react-sigma_core.esm.min-IsYtRW95.js} +1 -1
  24. package/assets/assets/{server-traffic-403Q1CKm.js → server-traffic-BtWOYZXf.js} +1 -1
  25. package/assets/assets/{service-ChEbo8oM.js → service-Bu4M0T0x.js} +1 -1
  26. package/assets/index.html +7 -7
  27. package/dist/{ads-6RVS23I5.js → ads-NS4KNR2U.js} +3 -3
  28. package/dist/{agent-N5LH7QNA.js → agent-773TJYGJ.js} +7 -7
  29. package/dist/{agent-ask-IJV2PWPN.js → agent-ask-MQXNRMAN.js} +3 -3
  30. package/dist/{agent-conversations-STISIDWB.js → agent-conversations-5EEEEWEQ.js} +3 -3
  31. package/dist/{agent-journeys-VCP5MD2W.js → agent-journeys-U5ITMUIM.js} +3 -3
  32. package/dist/{agent-memory-XQQNXRY6.js → agent-memory-6PEE54ST.js} +3 -3
  33. package/dist/{agent-providers-IW2Q6WPP.js → agent-providers-6D76GSYA.js} +3 -3
  34. package/dist/{agent-transcript-5XUCHOOT.js → agent-transcript-6M5RYZIR.js} +3 -3
  35. package/dist/{analytics-RZZZBC4H.js → analytics-WBFQJTHV.js} +3 -3
  36. package/dist/{apply-X5ZTHGSJ.js → apply-AGNDBHNV.js} +3 -3
  37. package/dist/{backfill-ZF6RX27O.js → backfill-ZDPS7VON.js} +10 -10
  38. package/dist/{backlinks-WZ7YUKHH.js → backlinks-TNPNJXPS.js} +3 -3
  39. package/dist/{bing-Z4A5NHOC.js → bing-5WB7JCBP.js} +3 -3
  40. package/dist/{bootstrap-OCJ7EIXK.js → bootstrap-XF77XL7U.js} +10 -10
  41. package/dist/{cdp-YGZFLTFT.js → cdp-NBTJLUPM.js} +7 -7
  42. package/dist/{chunk-RSBIHKHH.js → chunk-24CUAPPY.js} +4 -4
  43. package/dist/{chunk-M6DYUCNB.js → chunk-35AEUG77.js} +1 -1
  44. package/dist/{chunk-S6KJYLOV.js → chunk-377PJ4AT.js} +3 -3
  45. package/dist/{chunk-R7BX2ZSQ.js → chunk-3FHPUOMW.js} +1 -1
  46. package/dist/{chunk-K63XX7VY.js → chunk-4GECW7XZ.js} +3 -3
  47. package/dist/{chunk-3D4VEWDJ.js → chunk-5VLCPNTR.js} +6 -6
  48. package/dist/{chunk-SFNSSHIN.js → chunk-6X24IDPZ.js} +1 -1
  49. package/dist/{chunk-XPBEDIXW.js → chunk-CPJX2RWA.js} +2 -2
  50. package/dist/{chunk-PR5FCYNN.js → chunk-F2O42DSO.js} +21 -21
  51. package/dist/{chunk-D6KTO5TO.js → chunk-FUMYCZ62.js} +1 -1
  52. package/dist/{chunk-ECUJEKIO.js → chunk-G6JSK2RY.js} +1 -1
  53. package/dist/{chunk-YRCKRY3Q.js → chunk-HKUH75YH.js} +2 -2
  54. package/dist/{chunk-XIHIB7VP.js → chunk-HN2YNYPO.js} +7 -7
  55. package/dist/{chunk-IUDXV6PH.js → chunk-I663X7WB.js} +2 -2
  56. package/dist/{chunk-RJLSGCOI.js → chunk-IO4BZLM7.js} +2 -2
  57. package/dist/{chunk-BL4CZJYJ.js → chunk-JGLX5WU6.js} +115 -3
  58. package/dist/{chunk-NUBADAA4.js → chunk-JLC3CCFM.js} +4 -4
  59. package/dist/{chunk-VGAQH7KI.js → chunk-KCKUCJOG.js} +1 -1
  60. package/dist/{chunk-55EFIQP7.js → chunk-LXKWFM74.js} +2 -2
  61. package/dist/{chunk-N6MXZ5SC.js → chunk-MAOKDX3W.js} +5 -5
  62. package/dist/{chunk-UPBQHUE6.js → chunk-MJIL4BWR.js} +3 -3
  63. package/dist/{chunk-BJAOD3NW.js → chunk-N4SEITGT.js} +5 -5
  64. package/dist/{chunk-QDGW75MP.js → chunk-OCXDY2DG.js} +1 -1
  65. package/dist/{chunk-B5JKQXRN.js → chunk-PC7ZUW2H.js} +2 -2
  66. package/dist/{chunk-N7PGV6QQ.js → chunk-PSCXQGJE.js} +1027 -816
  67. package/dist/{chunk-LXJAXAJD.js → chunk-QBIIQF3J.js} +1 -1
  68. package/dist/{chunk-32N7BGTD.js → chunk-S3HNS2YJ.js} +3 -3
  69. package/dist/{chunk-CBNDEKI6.js → chunk-SDCH5EBA.js} +1 -1
  70. package/dist/{chunk-K2JCPF2W.js → chunk-SPWGDODX.js} +368 -368
  71. package/dist/{chunk-LDY66O7A.js → chunk-SXHD2NFT.js} +4 -4
  72. package/dist/{chunk-7JQM46YO.js → chunk-UPALIEP2.js} +2 -2
  73. package/dist/{chunk-NSUB2S4V.js → chunk-W4ZRFNOX.js} +57 -26
  74. package/dist/{chunk-JDOAULVX.js → chunk-XOCSSHYD.js} +1 -1
  75. package/dist/{chunk-FM74QGW5.js → chunk-YL77WFHQ.js} +7 -7
  76. package/dist/{chunk-YGHI3W4D.js → chunk-ZZVRDHSK.js} +1 -1
  77. package/dist/{citations-IUITVPHO.js → citations-MZ6XQDI3.js} +3 -3
  78. package/dist/{cli-catalog-IAPKU2RG.js → cli-catalog-QPB2ADGD.js} +3 -3
  79. package/dist/{cli-commands-FICINBUR.js → cli-commands-RRHOMF6X.js} +7 -7
  80. package/dist/cli.js +13 -13
  81. package/dist/{competitor-U5DMMCIA.js → competitor-VF77UMUR.js} +3 -3
  82. package/dist/{content-ZQIZUNF5.js → content-BINAMQFF.js} +3 -3
  83. package/dist/{dashboard-KPGX4EQT.js → dashboard-OE4BGMKP.js} +6 -6
  84. package/dist/{demo-NEWVCEXD.js → demo-3HVJUP3G.js} +1 -1
  85. package/dist/{demo-server-5GJEGG5K.js → demo-server-BEFPBA7P.js} +14 -14
  86. package/dist/{discover-HSPVFHKK.js → discover-3XUPBCXL.js} +3 -3
  87. package/dist/{discover-eval-BYKPJ3DL.js → discover-eval-3DWJPIU2.js} +3 -3
  88. package/dist/{doctor-CK4BTTWH.js → doctor-KYWVYS74.js} +3 -3
  89. package/dist/{evidence-BX3CWPCT.js → evidence-DCTXENHR.js} +3 -3
  90. package/dist/{export-cmd-QMCRDMPZ.js → export-cmd-E5OUTSMR.js} +3 -3
  91. package/dist/{ga-RMTUMSEJ.js → ga-KY7MHW2G.js} +4 -4
  92. package/dist/{gbp-JK67SYLW.js → gbp-2TJ6JACW.js} +3 -3
  93. package/dist/{google-PJSK6IJV.js → google-BZKYIIA6.js} +4 -4
  94. package/dist/{google-marketing-UQ2FONXU.js → google-marketing-6ACZP4JB.js} +1 -1
  95. package/dist/{health-cmd-ZDXFD53P.js → health-cmd-LCVJY5MG.js} +3 -3
  96. package/dist/{history-OYL7SPEY.js → history-MAZOPZZ5.js} +3 -3
  97. package/dist/index.d.ts +213 -35
  98. package/dist/index.js +25 -25
  99. package/dist/{init-DZUNAQBN.js → init-Z35R7A6X.js} +12 -12
  100. package/dist/{insights-AIBLDPX6.js → insights-PAZHZP74.js} +3 -3
  101. package/dist/intelligence-service-YA7PBFQV.js +16 -0
  102. package/dist/{journeys-EDEET2EC.js → journeys-H6OPK5XY.js} +3 -3
  103. package/dist/{keys-SEZQTYGJ.js → keys-DF6LV4T5.js} +3 -3
  104. package/dist/{keyword-6FJIWTHW.js → keyword-ILICCRQK.js} +3 -3
  105. package/dist/{logs-EML2KKOP.js → logs-PKKCPDIN.js} +3 -3
  106. package/dist/{mcp-AA5QUNBZ.js → mcp-7GVOYYX4.js} +4 -4
  107. package/dist/mcp.js +9 -9
  108. package/dist/{measurement-plan-ALHRGRPH.js → measurement-plan-V7E7UHE2.js} +5 -5
  109. package/dist/{notify-EVTI3SWQ.js → notify-ISU7XBAW.js} +3 -3
  110. package/dist/{organic-evidence-EXQQ6R7H.js → organic-evidence-FIL7YJ4Z.js} +3 -3
  111. package/dist/{overview-5CIFLSDG.js → overview-NVBLGY53.js} +4 -4
  112. package/dist/{project-DNDENHRU.js → project-NSNMLL3Z.js} +3 -3
  113. package/dist/{query-ETMHFVKZ.js → query-TPZUUJ4X.js} +3 -3
  114. package/dist/{research-Q5X46CR5.js → research-WILAIDCK.js} +3 -3
  115. package/dist/{results-export-DZZANVYY.js → results-export-XP7XUKI2.js} +3 -3
  116. package/dist/{run-OQJXRJ42.js → run-L646IKUB.js} +4 -4
  117. package/dist/{schedule-6AI3J2AB.js → schedule-I2LW344O.js} +3 -3
  118. package/dist/{search-7TAJBSHE.js → search-7EBWXQ6X.js} +3 -3
  119. package/dist/{serve-WD44V3TK.js → serve-LNBKTV5N.js} +26 -26
  120. package/dist/{session-LTKCS763.js → session-U2OPQSJE.js} +13 -13
  121. package/dist/{settings-EUMYGNIV.js → settings-IGJ6ZA7C.js} +3 -3
  122. package/dist/{sitemaps-I4QPLAZW.js → sitemaps-B4G4PXIE.js} +3 -3
  123. package/dist/{skills-PTYYU3FR.js → skills-WQXXY2HN.js} +2 -2
  124. package/dist/{snapshot-62FF4GEA.js → snapshot-2TD4WIEO.js} +3 -3
  125. package/dist/{src-LRYXRHLW.js → src-DI4RNYAG.js} +4 -2
  126. package/dist/{status-VRWPYTY3.js → status-SKGGPZFE.js} +4 -4
  127. package/dist/{technical-aeo-OKZW2GAX.js → technical-aeo-AFAXF5M5.js} +3 -3
  128. package/dist/{telemetry-RRTHKD6W.js → telemetry-ZRRX7I75.js} +4 -4
  129. package/dist/{tools-F7WCCKZW.js → tools-RY7ZXQJ5.js} +11 -11
  130. package/dist/{traffic-H6RDZWGG.js → traffic-LA3FZKID.js} +9 -9
  131. package/dist/{trends-RZOQW2LE.js → trends-GUUBTBCO.js} +3 -3
  132. package/dist/{users-KFBCBA3V.js → users-W44TRUSL.js} +3 -3
  133. package/dist/{visibility-stats-53OZR3UN.js → visibility-stats-KMKV4TF5.js} +3 -3
  134. package/dist/{wordpress-2BXU6FDI.js → wordpress-FNLXVJDO.js} +3 -3
  135. package/package.json +1 -1
  136. package/assets/assets/MeasurementPropertyPage-C1eIWJPD.js +0 -4
  137. package/assets/assets/ProjectPage-Ho61hE8f.js +0 -11
  138. package/assets/assets/SiteHealthSection-C2Ys7rzL.js +0 -2
  139. package/assets/assets/ToneBadge-C49669Ol.js +0 -60
  140. package/assets/assets/index-B-t8djQ-.js +0 -60
  141. package/assets/assets/react-query-dnbkXy7A.js +0 -1
  142. package/dist/intelligence-service-YLNZTJQA.js +0 -16
@@ -17,14 +17,14 @@ import {
17
17
  normalizeTrafficPathPattern,
18
18
  sessionizeJourneyRequests,
19
19
  verifyRequestSignature
20
- } from "./chunk-QDGW75MP.js";
20
+ } from "./chunk-OCXDY2DG.js";
21
21
  import {
22
22
  currentOutcomeAttribution,
23
23
  getRequestContext,
24
24
  registerRequestContext,
25
25
  requestAuditIdentity,
26
26
  sanitizeRequestContext
27
- } from "./chunk-CBNDEKI6.js";
27
+ } from "./chunk-SDCH5EBA.js";
28
28
  import {
29
29
  RawEventSampleEventTypes,
30
30
  SentimentIdempotencyConflict,
@@ -87,6 +87,7 @@ import {
87
87
  gaSearchLandingPages,
88
88
  gaSearchLandingWindows,
89
89
  gaSocialReferrals,
90
+ gaSyncCoverage,
90
91
  gaTrafficSnapshots,
91
92
  gaTrafficSummaries,
92
93
  gaTrafficWindowSummaries,
@@ -183,7 +184,7 @@ import {
183
184
  userSessions,
184
185
  users,
185
186
  writeNegativeReviewMaxStars
186
- } from "./chunk-BL4CZJYJ.js";
187
+ } from "./chunk-JGLX5WU6.js";
187
188
  import {
188
189
  ADS_ACTIVATE_SCOPE,
189
190
  ADS_ACTIVATION_ABSOLUTE_MAX_ENTITIES,
@@ -274,6 +275,8 @@ import {
274
275
  GA4_AI_ASSISTANT_CHANNEL_GROUP,
275
276
  GA_OVERVIEW_HISTORY_DAYS,
276
277
  GA_OVERVIEW_METRIC_ORDER,
278
+ GA_OVERVIEW_WINDOWS,
279
+ GA_OVERVIEW_WINDOW_DAYS,
277
280
  GA_SEARCH_LANDING_DEFAULT_LIMIT,
278
281
  GA_SEARCH_LANDING_DEFAULT_WINDOW,
279
282
  GA_SEARCH_LANDING_MAX_LIMIT,
@@ -1237,6 +1240,7 @@ import {
1237
1240
  trafficSampleClassifierSchema,
1238
1241
  trafficSamplesQuerySchema,
1239
1242
  trafficSamplesResponseSchema,
1243
+ trafficSeriesGranularitySchema,
1240
1244
  trafficSourceDetailDtoSchema,
1241
1245
  trafficSourceDtoSchema,
1242
1246
  trafficSourceListResponseSchema,
@@ -1280,7 +1284,7 @@ import {
1280
1284
  wordpressSchemaDeployResultDtoSchema,
1281
1285
  wordpressSchemaStatusResultDtoSchema,
1282
1286
  wordpressStatusDtoSchema
1283
- } from "./chunk-NSUB2S4V.js";
1287
+ } from "./chunk-W4ZRFNOX.js";
1284
1288
 
1285
1289
  // ../api-routes/src/user-session.ts
1286
1290
  import crypto2 from "crypto";
@@ -31582,17 +31586,7 @@ function emptyAiCounts() {
31582
31586
  }
31583
31587
  var AI_ATTRIBUTION_PREFERRED_DIMENSION = "session";
31584
31588
  function pickWinningAttributionDimension(sessionsByDimension) {
31585
- let winner;
31586
- let winnerSessions = -1;
31587
- for (const [dimension, sessions] of sessionsByDimension) {
31588
- const beatsOnCount = sessions > winnerSessions;
31589
- const winsTie = sessions === winnerSessions && dimension === AI_ATTRIBUTION_PREFERRED_DIMENSION;
31590
- if (beatsOnCount || winsTie) {
31591
- winnerSessions = sessions;
31592
- winner = dimension;
31593
- }
31594
- }
31595
- return winner;
31589
+ return sessionsByDimension.has(AI_ATTRIBUTION_PREFERRED_DIMENSION) ? AI_ATTRIBUTION_PREFERRED_DIMENSION : void 0;
31596
31590
  }
31597
31591
  function resolveWinningDimensions(rows) {
31598
31592
  const groups = /* @__PURE__ */ new Map();
@@ -36493,11 +36487,15 @@ var analyticsWindowParameter = {
36493
36487
  description: "Time window for analytics queries. An unrecognised value is rejected with 400; it is never widened to the full history.",
36494
36488
  schema: { type: "string", enum: ["7d", "30d", "90d", "all"] }
36495
36489
  };
36490
+ var gaTrafficWindowParameter = {
36491
+ ...analyticsWindowParameter,
36492
+ schema: { type: "string", enum: [...WINDOW_SETS.gaTraffic] }
36493
+ };
36496
36494
  var gaOverviewWindowParameter = {
36497
36495
  name: "window",
36498
36496
  in: "query",
36499
36497
  description: 'Overview window: the complete days ending yesterday, as GA4 counts "Last 30 days". Defaults to 30d. Any other value is rejected with 400.',
36500
- schema: { type: "string", enum: ["7d", "30d", "90d"] }
36498
+ schema: { type: "string", enum: [...WINDOW_SETS.gaOverview] }
36501
36499
  };
36502
36500
  var competitorLandscapeGroupKeyParameter = {
36503
36501
  name: "groupKey",
@@ -42757,7 +42755,7 @@ var routeCatalog = [
42757
42755
  {
42758
42756
  name: "window",
42759
42757
  in: "query",
42760
- description: `Stored window, ending yesterday in the GA4 property time zone. 28d matches GA4's default "Last 28 days".`,
42758
+ description: `Stored 7, 28, 90 or 180-day window, ending yesterday in the GA4 property time zone. 28d matches GA4's default "Last 28 days".`,
42761
42759
  schema: { type: "string", enum: [...gaSearchLandingWindowSchema.options], default: "28d" }
42762
42760
  },
42763
42761
  {
@@ -42812,7 +42810,7 @@ var routeCatalog = [
42812
42810
  summary: "Get GA4 landing page traffic, channel breakdown, and AI referral landing pages",
42813
42811
  description: "Every figure is measured over one window (windowStart to windowEnd). Each AI and social referral row carries share, its fraction of that table's sessions, so a table's shares add up to 1; each top page carries organicShare, its organic sessions over its own sessions.",
42814
42812
  tags: ["ga4"],
42815
- parameters: [nameParameter, limitQueryParameter, analyticsWindowParameter, analyticsStartDateParameter, analyticsEndDateParameter],
42813
+ parameters: [nameParameter, limitQueryParameter, gaTrafficWindowParameter, analyticsStartDateParameter, analyticsEndDateParameter],
42816
42814
  responses: {
42817
42815
  200: jsonResponse("GA4 traffic data returned.", "GaTrafficResponse"),
42818
42816
  400: errorResponse("GA4 is not connected."),
@@ -42825,7 +42823,7 @@ var routeCatalog = [
42825
42823
  summary: "Get raw AI referral detail rows per day, landing page, and attribution dimension",
42826
42824
  description: "Detail rows, not totals. One row per landing page per attribution dimension, so a single day of one source is many rows and each is commonly worth one session. Use /ga/ai-referral-daily for per-date or per-source session counts.",
42827
42825
  tags: ["ga4"],
42828
- parameters: [nameParameter, analyticsWindowParameter, analyticsStartDateParameter, analyticsEndDateParameter],
42826
+ parameters: [nameParameter, gaTrafficWindowParameter, analyticsStartDateParameter, analyticsEndDateParameter],
42829
42827
  responses: {
42830
42828
  200: jsonArrayResponse("AI referral history returned.", "GA4AiReferralHistoryEntry"),
42831
42829
  400: errorResponse("GA4 is not connected."),
@@ -42838,7 +42836,7 @@ var routeCatalog = [
42838
42836
  summary: "Get AI referral sessions per day and per source",
42839
42837
  description: "Sums landing pages within one attribution dimension and never across dimensions, so totalSessions equals the aiSessionsDeduped reported by /ga/traffic for the same window. Sessions only: GA counts users as a distinct count at the grain requested, so an AI-referral user total cannot be summed from these rows and no un-dimensioned AI-referral fetch exists to supply one.",
42840
42838
  tags: ["ga4"],
42841
- parameters: [nameParameter, analyticsWindowParameter, analyticsStartDateParameter, analyticsEndDateParameter],
42839
+ parameters: [nameParameter, gaTrafficWindowParameter, analyticsStartDateParameter, analyticsEndDateParameter],
42842
42840
  responses: {
42843
42841
  200: jsonResponse("AI referral daily series returned.", "GA4AiReferralDailyDto"),
42844
42842
  400: errorResponse("GA4 is not connected."),
@@ -42850,7 +42848,7 @@ var routeCatalog = [
42850
42848
  path: "/api/v1/projects/{name}/ga/social-referral-history",
42851
42849
  summary: "Get social media referral sessions per day grouped by source",
42852
42850
  tags: ["ga4"],
42853
- parameters: [nameParameter, analyticsWindowParameter, analyticsStartDateParameter, analyticsEndDateParameter],
42851
+ parameters: [nameParameter, gaTrafficWindowParameter, analyticsStartDateParameter, analyticsEndDateParameter],
42854
42852
  responses: {
42855
42853
  200: jsonArrayResponse("Social referral history returned.", "GA4SocialReferralHistoryEntry"),
42856
42854
  400: errorResponse("GA4 is not connected."),
@@ -42891,7 +42889,7 @@ var routeCatalog = [
42891
42889
  parameters: [nameParameter, gaOverviewWindowParameter],
42892
42890
  responses: {
42893
42891
  200: jsonResponse("GA4 overview returned.", "GaOverviewResponse"),
42894
- 400: errorResponse("GA4 is not connected, or the window is not 7d, 30d or 90d."),
42892
+ 400: errorResponse("GA4 is not connected, or the window is not 7d, 30d, 90d or 180d."),
42895
42893
  404: errorResponse("Project not found.")
42896
42894
  }
42897
42895
  },
@@ -42900,7 +42898,7 @@ var routeCatalog = [
42900
42898
  path: "/api/v1/projects/{name}/ga/session-history",
42901
42899
  summary: "Get total sessions per day for the project",
42902
42900
  tags: ["ga4"],
42903
- parameters: [nameParameter, analyticsWindowParameter, analyticsStartDateParameter, analyticsEndDateParameter],
42901
+ parameters: [nameParameter, gaTrafficWindowParameter, analyticsStartDateParameter, analyticsEndDateParameter],
42904
42902
  responses: {
42905
42903
  200: jsonArrayResponse("Session history returned.", "GA4SessionHistoryEntry"),
42906
42904
  400: errorResponse("GA4 is not connected."),
@@ -44003,7 +44001,7 @@ var routeCatalog = [
44003
44001
  { name: "kind", in: "query", description: 'Filter to "crawler", "ai-user-fetch", "ai-referral", a comma-separated list of them (e.g. "ai-user-fetch,ai-referral"), or "all" (default). Rows, totals and series cover only the kinds asked for.', schema: stringSchema },
44004
44002
  { name: "limit", in: "query", description: "Max newest combined detail rows in the events array (default 500, max 5000).", schema: stringSchema },
44005
44003
  { name: "sourceId", in: "query", description: "Restrict to a single traffic source.", schema: stringSchema },
44006
- { name: "granularity", in: "query", description: 'Full-window series bucket size: "hour" (default) or "day".', schema: { type: "string", enum: ["hour", "day"] } }
44004
+ { name: "granularity", in: "query", description: 'Full-window series bucket size: "hour" (default), "day" or "month" (UTC calendar months).', schema: { type: "string", enum: ["hour", "day", "month"] } }
44007
44005
  ],
44008
44006
  responses: {
44009
44007
  200: jsonResponse("Events returned with windowed totals.", "TrafficEventsResponse"),
@@ -47697,6 +47695,7 @@ var GA_PROPERTY_TABLES = [
47697
47695
  gaTrafficWindowSummaries,
47698
47696
  gaWindowMetrics,
47699
47697
  gaDailyTotals,
47698
+ gaSyncCoverage,
47700
47699
  gaAiReferrals,
47701
47700
  gaSocialReferrals,
47702
47701
  gaAcquisitionDaily,
@@ -47781,7 +47780,7 @@ var GA4_ADMIN_API_BASE = "https://analyticsadmin.googleapis.com/v1beta";
47781
47780
  var GA4_SCOPE = "https://www.googleapis.com/auth/analytics.readonly";
47782
47781
  var GOOGLE_TOKEN_URL2 = "https://oauth2.googleapis.com/token";
47783
47782
  var GA4_DEFAULT_SYNC_DAYS = 30;
47784
- var GA4_MAX_SYNC_DAYS = 90;
47783
+ var GA4_MAX_SYNC_DAYS = 180;
47785
47784
  function resolveGa4SyncDays(days) {
47786
47785
  const requestedDays = days != null && Number.isFinite(days) ? Math.trunc(days) : GA4_DEFAULT_SYNC_DAYS;
47787
47786
  const effectiveDays = Math.min(Math.max(1, requestedDays), GA4_MAX_SYNC_DAYS);
@@ -47859,6 +47858,7 @@ var GA4_METRICS = {
47859
47858
  };
47860
47859
  var GA4_SEARCH_LANDING_MAX_ROWS = 1e4;
47861
47860
  var GA4_SEARCH_LANDING_PAGE_SIZE = 1e4;
47861
+ var GA4_SEARCH_LANDING_REPORT_DEFINITION = "search-landing-v1";
47862
47862
 
47863
47863
  // ../integration-google-analytics/src/types.ts
47864
47864
  var GA4ApiError = class extends Error {
@@ -48163,7 +48163,12 @@ function reportTimeZone(...responses) {
48163
48163
  }
48164
48164
  return null;
48165
48165
  }
48166
- function measurementDateRange(days) {
48166
+ function measurementDateRange(days, requestedRange, maxDays = GA4_MAX_SYNC_DAYS) {
48167
+ if (requestedRange) {
48168
+ const count10 = inclusiveDayCount(requestedRange.startDate, requestedRange.endDate);
48169
+ if (count10 === null || count10 > maxDays + 1) throw new GA4ApiError("Invalid GA4 sync date range", 400);
48170
+ return { syncDays: count10, dateRange: requestedRange };
48171
+ }
48167
48172
  const { effectiveDays: syncDays } = resolveGa4SyncDays(days);
48168
48173
  const endDate = /* @__PURE__ */ new Date();
48169
48174
  const startDate = /* @__PURE__ */ new Date();
@@ -48211,7 +48216,7 @@ async function fetchPagedReportWithTotals(accessToken, propertyId, baseRequest,
48211
48216
  async function fetchAcquisitionByChannel(accessToken, propertyId, days, options) {
48212
48217
  validateAccessToken2(accessToken);
48213
48218
  validatePropertyId(propertyId);
48214
- const { syncDays, dateRange } = measurementDateRange(days);
48219
+ const { syncDays, dateRange } = measurementDateRange(days, options?.dateRange);
48215
48220
  const rows = await fetchPagedReport(accessToken, propertyId, {
48216
48221
  dateRanges: [dateRange],
48217
48222
  dimensions: [
@@ -48238,10 +48243,10 @@ async function fetchAcquisitionByChannel(accessToken, propertyId, days, options)
48238
48243
  function isLandingPageDimensionIncompatibility(error) {
48239
48244
  return error instanceof GA4ApiError && error.status === 400 && /landingPagePlusQueryString/i.test(error.message) && /incompatib/i.test(error.message);
48240
48245
  }
48241
- async function fetchLeadEvents(accessToken, propertyId, eventNames, days) {
48246
+ async function fetchLeadEvents(accessToken, propertyId, eventNames, days, dateRangeOverride) {
48242
48247
  validateAccessToken2(accessToken);
48243
48248
  validatePropertyId(propertyId);
48244
- const { dateRange } = measurementDateRange(days);
48249
+ const { dateRange } = measurementDateRange(days, dateRangeOverride);
48245
48250
  const filter = { orGroup: { expressions: eventNames.map((value) => ({ filter: {
48246
48251
  fieldName: GA4_DIMENSIONS.eventName,
48247
48252
  stringFilter: { matchType: "EXACT", value }
@@ -48327,13 +48332,10 @@ var AI_REFERRAL_PASSES = [
48327
48332
  function isInvalidGa4Request(error) {
48328
48333
  return error instanceof GA4ApiError && error.status === 400;
48329
48334
  }
48330
- async function fetchTrafficByLandingPage(accessToken, propertyId, days) {
48335
+ async function fetchTrafficByLandingPage(accessToken, propertyId, days, dateRangeOverride) {
48331
48336
  validateAccessToken2(accessToken);
48332
48337
  validatePropertyId(propertyId);
48333
- const { effectiveDays: syncDays } = resolveGa4SyncDays(days);
48334
- const endDate = /* @__PURE__ */ new Date();
48335
- const startDate = /* @__PURE__ */ new Date();
48336
- startDate.setDate(startDate.getDate() - syncDays);
48338
+ const { syncDays, dateRange } = measurementDateRange(days, dateRangeOverride);
48337
48339
  ga4Log("info", "fetch-traffic.start", { propertyId, days: syncDays });
48338
48340
  const PAGE_SIZE = 1e4;
48339
48341
  const rows = [];
@@ -48342,7 +48344,7 @@ async function fetchTrafficByLandingPage(accessToken, propertyId, days) {
48342
48344
  while (pageCount < GA4_MAX_PAGES) {
48343
48345
  pageCount++;
48344
48346
  const request = {
48345
- dateRanges: [{ startDate: formatDate(startDate), endDate: formatDate(endDate) }],
48347
+ dateRanges: [dateRange],
48346
48348
  dimensions: [
48347
48349
  { name: GA4_DIMENSIONS.date },
48348
48350
  { name: GA4_DIMENSIONS.landingPagePlusQueryString }
@@ -48367,9 +48369,10 @@ async function fetchTrafficByLandingPage(accessToken, propertyId, days) {
48367
48369
  users: parseInt(row.metricValues[1].value, 10) || 0
48368
48370
  }));
48369
48371
  rows.push(...pageRows);
48370
- const totalRows = response.rowCount ?? 0;
48372
+ const totalRows = response.rowCount;
48371
48373
  offset += pageRows.length;
48372
- if (pageRows.length < PAGE_SIZE || offset >= totalRows) break;
48374
+ if (pageRows.length < PAGE_SIZE || totalRows !== void 0 && offset >= totalRows) break;
48375
+ if (pageCount >= GA4_MAX_PAGES) throw new GA4ApiError("GA4 report truncated at the page limit", 502);
48373
48376
  }
48374
48377
  const organicMap = /* @__PURE__ */ new Map();
48375
48378
  let organicOffset = 0;
@@ -48377,7 +48380,7 @@ async function fetchTrafficByLandingPage(accessToken, propertyId, days) {
48377
48380
  while (organicPageCount < GA4_MAX_PAGES) {
48378
48381
  organicPageCount++;
48379
48382
  const organicRequest = {
48380
- dateRanges: [{ startDate: formatDate(startDate), endDate: formatDate(endDate) }],
48383
+ dateRanges: [dateRange],
48381
48384
  dimensions: [{ name: GA4_DIMENSIONS.date }, { name: GA4_DIMENSIONS.landingPagePlusQueryString }],
48382
48385
  metrics: [{ name: GA4_METRICS.sessions }],
48383
48386
  dimensionFilter: {
@@ -48395,9 +48398,10 @@ async function fetchTrafficByLandingPage(accessToken, propertyId, days) {
48395
48398
  const key = `${row.dimensionValues[0].value}::${row.dimensionValues[1].value}`;
48396
48399
  organicMap.set(key, parseInt(row.metricValues[0].value, 10) || 0);
48397
48400
  }
48398
- const total = organicResponse.rowCount ?? 0;
48401
+ const total = organicResponse.rowCount;
48399
48402
  organicOffset += (organicResponse.rows ?? []).length;
48400
- if ((organicResponse.rows ?? []).length < 1e4 || organicOffset >= total) break;
48403
+ if ((organicResponse.rows ?? []).length < 1e4 || total !== void 0 && organicOffset >= total) break;
48404
+ if (organicPageCount >= GA4_MAX_PAGES) throw new GA4ApiError("GA4 organic report truncated at the page limit", 502);
48401
48405
  }
48402
48406
  const directMap = /* @__PURE__ */ new Map();
48403
48407
  let directOffset = 0;
@@ -48405,7 +48409,7 @@ async function fetchTrafficByLandingPage(accessToken, propertyId, days) {
48405
48409
  while (directPageCount < GA4_MAX_PAGES) {
48406
48410
  directPageCount++;
48407
48411
  const directRequest = {
48408
- dateRanges: [{ startDate: formatDate(startDate), endDate: formatDate(endDate) }],
48412
+ dateRanges: [dateRange],
48409
48413
  dimensions: [{ name: GA4_DIMENSIONS.date }, { name: GA4_DIMENSIONS.landingPagePlusQueryString }],
48410
48414
  metrics: [{ name: GA4_METRICS.sessions }],
48411
48415
  dimensionFilter: {
@@ -48423,9 +48427,10 @@ async function fetchTrafficByLandingPage(accessToken, propertyId, days) {
48423
48427
  const key = `${row.dimensionValues[0].value}::${row.dimensionValues[1].value}`;
48424
48428
  directMap.set(key, parseInt(row.metricValues[0].value, 10) || 0);
48425
48429
  }
48426
- const total = directResponse.rowCount ?? 0;
48430
+ const total = directResponse.rowCount;
48427
48431
  directOffset += (directResponse.rows ?? []).length;
48428
- if ((directResponse.rows ?? []).length < 1e4 || directOffset >= total) break;
48432
+ if ((directResponse.rows ?? []).length < 1e4 || total !== void 0 && directOffset >= total) break;
48433
+ if (directPageCount >= GA4_MAX_PAGES) throw new GA4ApiError("GA4 direct report truncated at the page limit", 502);
48429
48434
  }
48430
48435
  for (const row of rows) {
48431
48436
  const key = `${row.date}::${row.landingPage}`;
@@ -48592,7 +48597,8 @@ async function fetchDailyTotals(accessToken, propertyId, days = GA_OVERVIEW_HIST
48592
48597
  validatePropertyId(propertyId);
48593
48598
  const historyDays = Math.min(Math.max(1, Number.isFinite(days) ? Math.trunc(days) : GA_OVERVIEW_HISTORY_DAYS), GA_OVERVIEW_HISTORY_DAYS);
48594
48599
  const requestedAt = (options.now ?? /* @__PURE__ */ new Date()).toISOString();
48595
- const dateRange = { startDate: ga4RelativeDate(historyDays), endDate: ga4RelativeDate(0) };
48600
+ const dateRange = options.dateRange ? measurementDateRange(days, options.dateRange, GA_OVERVIEW_HISTORY_DAYS).dateRange : { startDate: ga4RelativeDate(historyDays), endDate: ga4RelativeDate(0) };
48601
+ const rowLimit = options.dateRange ? inclusiveDayCount(dateRange.startDate, dateRange.endDate) : historyDays + 1;
48596
48602
  ga4Log("info", "fetch-daily-totals.start", { propertyId, days: historyDays });
48597
48603
  const [totalsRes, organicRes] = await batchRunReports(accessToken, propertyId, [
48598
48604
  {
@@ -48610,7 +48616,7 @@ async function fetchDailyTotals(accessToken, propertyId, days = GA_OVERVIEW_HIST
48610
48616
  { name: GA4_METRICS.userEngagementDuration },
48611
48617
  { name: GA4_METRICS.bounceRate }
48612
48618
  ],
48613
- limit: historyDays + 1
48619
+ limit: rowLimit
48614
48620
  },
48615
48621
  {
48616
48622
  dateRanges: [dateRange],
@@ -48622,7 +48628,7 @@ async function fetchDailyTotals(accessToken, propertyId, days = GA_OVERVIEW_HIST
48622
48628
  stringFilter: { matchType: "EXACT", value: "Organic Search" }
48623
48629
  }
48624
48630
  },
48625
- limit: historyDays + 1
48631
+ limit: rowLimit
48626
48632
  }
48627
48633
  ]);
48628
48634
  const organicByDate = /* @__PURE__ */ new Map();
@@ -48652,18 +48658,17 @@ async function fetchDailyTotals(accessToken, propertyId, days = GA_OVERVIEW_HIST
48652
48658
  const timeZone = reportTimeZone(totalsRes, organicRes) ?? "UTC";
48653
48659
  ga4Log("info", "fetch-daily-totals.done", { propertyId, days: historyDays, timeZone, rows: rows.length });
48654
48660
  return {
48655
- startDate: isoDateDaysBeforeInTimeZone(requestedAt, historyDays, timeZone),
48656
- endDate: isoDateDaysBeforeInTimeZone(requestedAt, 0, timeZone),
48661
+ startDate: options.dateRange?.startDate ?? isoDateDaysBeforeInTimeZone(requestedAt, historyDays, timeZone),
48662
+ endDate: options.dateRange?.endDate ?? isoDateDaysBeforeInTimeZone(requestedAt, 0, timeZone),
48657
48663
  rows
48658
48664
  };
48659
48665
  }
48660
- var WINDOW_DAYS2 = { "7d": 7, "30d": 30, "90d": 90 };
48661
48666
  async function fetchWindowSummary(accessToken, propertyId, windowKey) {
48662
48667
  validateAccessToken2(accessToken);
48663
48668
  validatePropertyId(propertyId);
48664
- const days = WINDOW_DAYS2[windowKey];
48669
+ const days = GA_OVERVIEW_WINDOW_DAYS[windowKey];
48665
48670
  if (!days) {
48666
- throw new GA4ApiError(`Unsupported windowKey "${windowKey}" \u2014 must be 7d, 30d, or 90d`, 400);
48671
+ throw new GA4ApiError(`Unsupported windowKey "${windowKey}" \u2014 must be 7d, 30d, 90d, or 180d`, 400);
48667
48672
  }
48668
48673
  const endDate = /* @__PURE__ */ new Date();
48669
48674
  const startDate = /* @__PURE__ */ new Date();
@@ -48793,16 +48798,12 @@ async function fetchWindowMetrics(accessToken, propertyId, windowKey, options =
48793
48798
  previous: period2(COMPARISON_RANGE_NAMES.previous, ranges.previous)
48794
48799
  };
48795
48800
  ga4Log("info", "fetch-window-metrics.done", { propertyId, windowKey, timeZone });
48796
- return result;
48801
+ return { ...result, timeZone };
48797
48802
  }
48798
- async function fetchAiReferrals(accessToken, propertyId, days) {
48803
+ async function fetchAiReferrals(accessToken, propertyId, days, dateRangeOverride) {
48799
48804
  validateAccessToken2(accessToken);
48800
48805
  validatePropertyId(propertyId);
48801
- const { effectiveDays: syncDays } = resolveGa4SyncDays(days);
48802
- const endDate = /* @__PURE__ */ new Date();
48803
- const startDate = /* @__PURE__ */ new Date();
48804
- startDate.setDate(startDate.getDate() - syncDays);
48805
- const dateRange = { startDate: formatDate(startDate), endDate: formatDate(endDate) };
48806
+ const { syncDays, dateRange } = measurementDateRange(days, dateRangeOverride);
48806
48807
  ga4Log("info", "fetch-ai-referrals.start", { propertyId, days: syncDays });
48807
48808
  const rows = [];
48808
48809
  for (const pass of AI_REFERRAL_PASSES) {
@@ -48900,20 +48901,18 @@ async function fetchAiReferralPass(accessToken, propertyId, dateRange, pass, cha
48900
48901
  };
48901
48902
  });
48902
48903
  rows.push(...pageRows);
48903
- const totalRows = response.rowCount ?? 0;
48904
+ const totalRows = response.rowCount;
48904
48905
  offset += pageRows.length;
48905
- if (pageRows.length < PAGE_SIZE || offset >= totalRows) break;
48906
+ if (pageRows.length < PAGE_SIZE || totalRows !== void 0 && offset >= totalRows) break;
48907
+ if (pageCount >= GA4_MAX_PAGES) throw new GA4ApiError("GA4 report truncated at the page limit", 502);
48906
48908
  }
48907
48909
  return rows;
48908
48910
  }
48909
48911
  var SOCIAL_CHANNEL_GROUPS = ["Organic Social", "Paid Social"];
48910
- async function fetchSocialReferrals(accessToken, propertyId, days) {
48912
+ async function fetchSocialReferrals(accessToken, propertyId, days, dateRangeOverride) {
48911
48913
  validateAccessToken2(accessToken);
48912
48914
  validatePropertyId(propertyId);
48913
- const { effectiveDays: syncDays } = resolveGa4SyncDays(days);
48914
- const endDate = /* @__PURE__ */ new Date();
48915
- const startDate = /* @__PURE__ */ new Date();
48916
- startDate.setDate(startDate.getDate() - syncDays);
48915
+ const { syncDays, dateRange } = measurementDateRange(days, dateRangeOverride);
48917
48916
  ga4Log("info", "fetch-social-referrals.start", { propertyId, days: syncDays });
48918
48917
  const PAGE_SIZE = 1e3;
48919
48918
  const rows = [];
@@ -48921,7 +48920,7 @@ async function fetchSocialReferrals(accessToken, propertyId, days) {
48921
48920
  let pageCount = 0;
48922
48921
  while (pageCount < GA4_MAX_PAGES) {
48923
48922
  const request = {
48924
- dateRanges: [{ startDate: formatDate(startDate), endDate: formatDate(endDate) }],
48923
+ dateRanges: [dateRange],
48925
48924
  dimensions: [
48926
48925
  { name: GA4_DIMENSIONS.date },
48927
48926
  { name: GA4_DIMENSIONS.sessionSource },
@@ -48956,10 +48955,11 @@ async function fetchSocialReferrals(accessToken, propertyId, days) {
48956
48955
  channelGroup: row.dimensionValues[3].value
48957
48956
  }));
48958
48957
  rows.push(...pageRows);
48959
- const totalRows = response.rowCount ?? 0;
48958
+ const totalRows = response.rowCount;
48960
48959
  offset += pageRows.length;
48961
48960
  pageCount += 1;
48962
- if (pageRows.length < PAGE_SIZE || offset >= totalRows) break;
48961
+ if (pageRows.length < PAGE_SIZE || totalRows !== void 0 && offset >= totalRows) break;
48962
+ if (pageCount >= GA4_MAX_PAGES) throw new GA4ApiError("GA4 report truncated at the page limit", 502);
48963
48963
  }
48964
48964
  for (const row of rows) {
48965
48965
  row.date = compactDateToIso(row.date);
@@ -49012,10 +49012,14 @@ function isSearchConsoleUnavailable(error) {
49012
49012
  }
49013
49013
  async function fetchSearchLandingWindow(accessToken, propertyId, window2, options) {
49014
49014
  const days = GA_SEARCH_LANDING_WINDOW_DAYS[window2];
49015
+ const explicitRange = options.timeZone === void 0 ? null : {
49016
+ startDate: isoDateDaysBeforeInTimeZone(options.requestedAt, days, options.timeZone),
49017
+ endDate: isoDateDaysBeforeInTimeZone(options.requestedAt, 1, options.timeZone)
49018
+ };
49015
49019
  const { rows: rawRows, firstPage, reportRowCount, capped } = await fetchPagedReportWithTotals(accessToken, propertyId, {
49016
- // Relative dates resolve in the property's reporting time zone, which is
49017
- // how GA4's own "Last N days" ranges end: yesterday, local to the property.
49018
- dateRanges: [{ startDate: `${days}daysAgo`, endDate: "yesterday" }],
49020
+ // A known reporting zone pins the planner's calendar bounds across I/O.
49021
+ // Otherwise GA4 resolves the relative range in its own reporting zone.
49022
+ dateRanges: [explicitRange ?? { startDate: `${days}daysAgo`, endDate: "yesterday" }],
49019
49023
  dimensions: [{ name: GA4_DIMENSIONS.landingPagePlusQueryString }],
49020
49024
  metrics: SEARCH_LANDING_METRIC_NAMES.map((name) => ({ name })),
49021
49025
  metricAggregations: ["TOTAL"],
@@ -49047,8 +49051,8 @@ async function fetchSearchLandingWindow(accessToken, propertyId, window2, option
49047
49051
  const zone = timeZone ?? "UTC";
49048
49052
  return {
49049
49053
  window: window2,
49050
- periodStart: isoDateDaysBeforeInTimeZone(options.requestedAt, days, zone),
49051
- periodEnd: isoDateDaysBeforeInTimeZone(options.requestedAt, 1, zone),
49054
+ periodStart: explicitRange?.startDate ?? isoDateDaysBeforeInTimeZone(options.requestedAt, days, zone),
49055
+ periodEnd: explicitRange?.endDate ?? isoDateDaysBeforeInTimeZone(options.requestedAt, 1, zone),
49052
49056
  timeZone,
49053
49057
  total,
49054
49058
  rows,
@@ -49062,13 +49066,17 @@ async function fetchSearchLandingWindow(accessToken, propertyId, window2, option
49062
49066
  async function fetchSearchLandingPages(accessToken, propertyId, options = {}) {
49063
49067
  validateAccessToken2(accessToken);
49064
49068
  validatePropertyId(propertyId);
49069
+ const requestedWindows = options.windows ?? GA_SEARCH_LANDING_WINDOWS;
49070
+ if (requestedWindows.length === 0 || new Set(requestedWindows).size !== requestedWindows.length || requestedWindows.some((window2) => !GA_SEARCH_LANDING_WINDOWS.includes(window2))) {
49071
+ throw new GA4ApiError("Search landing windows must be a non-empty selection of unique supported windows", 400);
49072
+ }
49065
49073
  const requestedAt = (options.now ?? /* @__PURE__ */ new Date()).toISOString();
49066
49074
  const pageSize = options.pageSize ?? GA4_SEARCH_LANDING_PAGE_SIZE;
49067
49075
  const maxRows = options.maxRows ?? GA4_SEARCH_LANDING_MAX_ROWS;
49068
49076
  ga4Log("info", "fetch-search-landing.start", { propertyId });
49069
49077
  let windows;
49070
49078
  try {
49071
- windows = await Promise.all(GA_SEARCH_LANDING_WINDOWS.map((window2) => fetchSearchLandingWindow(accessToken, propertyId, window2, { requestedAt, pageSize, maxRows })));
49079
+ windows = await Promise.all(requestedWindows.map((window2) => fetchSearchLandingWindow(accessToken, propertyId, window2, { requestedAt, pageSize, maxRows, timeZone: options.timeZone })));
49072
49080
  } catch (error) {
49073
49081
  if (!isSearchConsoleUnavailable(error)) throw error;
49074
49082
  ga4Log("info", "fetch-search-landing.unavailable", { propertyId, httpStatus: 400 });
@@ -59608,7 +59616,7 @@ async function cdpRoutes(app, opts) {
59608
59616
 
59609
59617
  // ../api-routes/src/ga.ts
59610
59618
  import crypto51 from "crypto";
59611
- import { eq as eq85, desc as desc38, and as and73, sql as sql37 } from "drizzle-orm";
59619
+ import { eq as eq86, desc as desc38, and as and74, sql as sql37 } from "drizzle-orm";
59612
59620
 
59613
59621
  // ../api-routes/src/ga-session-history.ts
59614
59622
  function buildSessionHistory(landingPageDays, dailyTotals) {
@@ -59628,7 +59636,7 @@ function buildSessionHistory(landingPageDays, dailyTotals) {
59628
59636
  // ../api-routes/src/ga-overview.ts
59629
59637
  function parseGaOverviewWindow(value) {
59630
59638
  if (value === void 0 || value === "") return "30d";
59631
- if (!isGaOverviewWindow(value)) throw validationError(`Invalid window "${value}": the GA4 overview supports 7d, 30d or 90d.`);
59639
+ if (!isGaOverviewWindow(value)) throw validationError(`Invalid window "${value}": the GA4 overview supports 7d, 30d, 90d or 180d.`);
59632
59640
  return value;
59633
59641
  }
59634
59642
  var NO_VALUES = {
@@ -59771,13 +59779,93 @@ function findBiggestMover(current, prior) {
59771
59779
  return mover;
59772
59780
  }
59773
59781
 
59782
+ // ../api-routes/src/ga-sync-coverage.ts
59783
+ import { and as and72, eq as eq84, lt as lt17 } from "drizzle-orm";
59784
+ function planGaSyncCoverage(db, input, now) {
59785
+ const definition2 = input.definition ?? "v1";
59786
+ const refreshStart = shiftIsoCalendarDate(input.requested.endDate, -13);
59787
+ const reconcileAfter = new Date(Date.parse(now) - 30 * 864e5).toISOString();
59788
+ const covered = new Set(db.select().from(gaSyncCoverage).where(and72(
59789
+ eq84(gaSyncCoverage.projectId, input.projectId),
59790
+ eq84(gaSyncCoverage.propertyId, input.propertyId),
59791
+ eq84(gaSyncCoverage.component, input.component),
59792
+ eq84(gaSyncCoverage.definition, definition2)
59793
+ )).all().filter((row) => row.settled && row.syncedAt > reconcileAfter).map((row) => row.date));
59794
+ const missing = calendarDateRange(input.requested.startDate, input.requested.endDate).filter((date) => date >= refreshStart || !covered.has(date));
59795
+ return {
59796
+ ...input,
59797
+ definition: definition2,
59798
+ refreshStart,
59799
+ ranges: groupIsoDateRanges(missing).map((range) => ({ startDate: range.start, endDate: range.end }))
59800
+ };
59801
+ }
59802
+ function saveGaSyncCoverage(tx, plan, ranges, syncedAt, syncRunId) {
59803
+ for (const range of ranges) {
59804
+ for (const date of calendarDateRange(range.startDate, range.endDate)) {
59805
+ const values = {
59806
+ projectId: plan.projectId,
59807
+ propertyId: plan.propertyId,
59808
+ component: plan.component,
59809
+ definition: plan.definition,
59810
+ date,
59811
+ settled: date < plan.refreshStart,
59812
+ syncedAt,
59813
+ syncRunId
59814
+ };
59815
+ tx.insert(gaSyncCoverage).values(values).onConflictDoUpdate({
59816
+ target: [gaSyncCoverage.projectId, gaSyncCoverage.component, gaSyncCoverage.date],
59817
+ set: values
59818
+ }).run();
59819
+ }
59820
+ }
59821
+ tx.delete(gaSyncCoverage).where(and72(
59822
+ eq84(gaSyncCoverage.projectId, plan.projectId),
59823
+ lt17(gaSyncCoverage.date, shiftIsoCalendarDate(plan.requested.endDate, -362))
59824
+ )).run();
59825
+ }
59826
+ async function fetchGaSyncRanges(plan, fetchRange) {
59827
+ const results = [];
59828
+ for (const range of plan.ranges) {
59829
+ results.push({ range, report: await fetchRange(range) });
59830
+ }
59831
+ return results;
59832
+ }
59833
+
59774
59834
  // ../api-routes/src/ga-search-landing-pages.ts
59775
59835
  import crypto50 from "crypto";
59776
- import { and as and72, asc as asc21, count as count5, desc as desc37, eq as eq84, gte as gte23, lte as lte23, sql as sql36 } from "drizzle-orm";
59836
+ import { and as and73, asc as asc21, count as count5, desc as desc37, eq as eq85, gte as gte23, lte as lte23, sql as sql36 } from "drizzle-orm";
59837
+ function searchLandingWindowsToFetch(db, projectId, propertyId, timeZone, requestedAt) {
59838
+ const state = db.select().from(gaMeasurementSyncStates).where(eq85(gaMeasurementSyncStates.projectId, projectId)).get();
59839
+ if (!timeZone || state?.searchLandingStatus !== "ready") return [...GA_SEARCH_LANDING_WINDOWS];
59840
+ const stored = db.select().from(gaSearchLandingWindows).where(eq85(gaSearchLandingWindows.projectId, projectId)).all();
59841
+ const now = Date.parse(requestedAt);
59842
+ const end = isoDateDaysBeforeInTimeZone(requestedAt, 1, timeZone);
59843
+ return GA_SEARCH_LANDING_WINDOWS.filter((window2) => {
59844
+ const snapshot = stored.find((row) => row.windowKey === window2);
59845
+ if (!snapshot) return true;
59846
+ const age = now - Date.parse(snapshot.syncedAt);
59847
+ return snapshot.propertyId !== propertyId || snapshot.reportDefinition !== GA4_SEARCH_LANDING_REPORT_DEFINITION || snapshot.timeZone !== timeZone || snapshot.periodStart !== isoDateDaysBeforeInTimeZone(requestedAt, GA_SEARCH_LANDING_WINDOW_DAYS[window2], timeZone) || snapshot.periodEnd !== end || !(age >= 0 && age < 60 * 6e4);
59848
+ });
59849
+ }
59777
59850
  function persistSearchLandingSnapshot(db, input) {
59778
59851
  db.transaction((tx) => {
59779
- deleteSearchLandingRows(tx, input.projectId);
59852
+ if (input.reuse.windows.length > 0) {
59853
+ const missing = searchLandingWindowsToFetch(tx, input.projectId, input.propertyId, input.reuse.timeZone, input.reuse.requestedAt);
59854
+ if (missing.some((window2) => input.reuse.windows.includes(window2))) {
59855
+ throw new Error("Search landing snapshots changed during sync; retry to refresh the missing windows");
59856
+ }
59857
+ }
59858
+ const otherProperty = tx.select({ propertyId: gaSearchLandingWindows.propertyId }).from(gaSearchLandingWindows).where(eq85(gaSearchLandingWindows.projectId, input.projectId)).all().some((row) => row.propertyId !== input.propertyId);
59859
+ if (otherProperty) deleteSearchLandingRows(tx, input.projectId);
59780
59860
  for (const window2 of input.windows) {
59861
+ tx.delete(gaSearchLandingPages).where(and73(
59862
+ eq85(gaSearchLandingPages.projectId, input.projectId),
59863
+ eq85(gaSearchLandingPages.windowKey, window2.window)
59864
+ )).run();
59865
+ tx.delete(gaSearchLandingWindows).where(and73(
59866
+ eq85(gaSearchLandingWindows.projectId, input.projectId),
59867
+ eq85(gaSearchLandingWindows.windowKey, window2.window)
59868
+ )).run();
59781
59869
  tx.insert(gaSearchLandingWindows).values({
59782
59870
  id: crypto50.randomUUID(),
59783
59871
  projectId: input.projectId,
@@ -59786,6 +59874,7 @@ function persistSearchLandingSnapshot(db, input) {
59786
59874
  periodStart: window2.periodStart,
59787
59875
  periodEnd: window2.periodEnd,
59788
59876
  timeZone: window2.timeZone,
59877
+ reportDefinition: GA4_SEARCH_LANDING_REPORT_DEFINITION,
59789
59878
  totalClicks: window2.total.clicks,
59790
59879
  totalImpressions: window2.total.impressions,
59791
59880
  totalCtr: window2.total.ctr,
@@ -59819,7 +59908,7 @@ function persistSearchLandingSnapshot(db, input) {
59819
59908
  upsertSearchLandingState(tx, input.projectId, {
59820
59909
  searchLandingStatus: "ready",
59821
59910
  searchLandingError: null,
59822
- searchLandingSyncedAt: input.syncedAt,
59911
+ ...input.windows.length > 0 ? { searchLandingSyncedAt: input.syncedAt } : {},
59823
59912
  searchLandingAttemptedAt: input.syncedAt
59824
59913
  }, input.syncedAt);
59825
59914
  });
@@ -59837,7 +59926,7 @@ function persistSearchLandingSnapshot(db, input) {
59837
59926
  }
59838
59927
  function persistSearchLandingFailure(db, projectId, propertyId, status, message, attemptedAt) {
59839
59928
  db.transaction((tx) => {
59840
- const storedProperties = tx.selectDistinct({ propertyId: gaSearchLandingWindows.propertyId }).from(gaSearchLandingWindows).where(eq84(gaSearchLandingWindows.projectId, projectId)).all();
59929
+ const storedProperties = tx.selectDistinct({ propertyId: gaSearchLandingWindows.propertyId }).from(gaSearchLandingWindows).where(eq85(gaSearchLandingWindows.projectId, projectId)).all();
59841
59930
  const otherProperty = storedProperties.some((stored) => stored.propertyId !== propertyId);
59842
59931
  if (otherProperty) deleteSearchLandingRows(tx, projectId);
59843
59932
  upsertSearchLandingState(tx, projectId, {
@@ -59849,8 +59938,8 @@ function persistSearchLandingFailure(db, projectId, propertyId, status, message,
59849
59938
  });
59850
59939
  }
59851
59940
  function deleteSearchLandingRows(db, projectId) {
59852
- db.delete(gaSearchLandingPages).where(eq84(gaSearchLandingPages.projectId, projectId)).run();
59853
- db.delete(gaSearchLandingWindows).where(eq84(gaSearchLandingWindows.projectId, projectId)).run();
59941
+ db.delete(gaSearchLandingPages).where(eq85(gaSearchLandingPages.projectId, projectId)).run();
59942
+ db.delete(gaSearchLandingWindows).where(eq85(gaSearchLandingWindows.projectId, projectId)).run();
59854
59943
  }
59855
59944
  function upsertSearchLandingState(db, projectId, fields, updatedAt) {
59856
59945
  db.insert(gaMeasurementSyncStates).values({ projectId, ...fields, updatedAt }).onConflictDoUpdate({
@@ -59882,11 +59971,11 @@ function buildGaSearchLandingPages(db, projectName, query2, connections) {
59882
59971
  throw validationError('"offset" must be a non-negative integer');
59883
59972
  }
59884
59973
  const project = resolveProject(db, projectName);
59885
- const storedWindow = db.select().from(gaSearchLandingWindows).where(and72(eq84(gaSearchLandingWindows.projectId, project.id), eq84(gaSearchLandingWindows.windowKey, window2))).get();
59974
+ const storedWindow = db.select().from(gaSearchLandingWindows).where(and73(eq85(gaSearchLandingWindows.projectId, project.id), eq85(gaSearchLandingWindows.windowKey, window2))).get();
59886
59975
  const otherProperty = storedWindow !== void 0 && storedWindow.propertyId !== resolveCurrentGa4PropertyId(connections, project.name, project.canonicalDomain);
59887
59976
  const stored = otherProperty ? void 0 : storedWindow;
59888
- const state = otherProperty ? void 0 : db.select().from(gaMeasurementSyncStates).where(eq84(gaMeasurementSyncStates.projectId, project.id)).get();
59889
- const pageScope = and72(eq84(gaSearchLandingPages.projectId, project.id), eq84(gaSearchLandingPages.windowKey, window2));
59977
+ const state = otherProperty ? void 0 : db.select().from(gaMeasurementSyncStates).where(eq85(gaMeasurementSyncStates.projectId, project.id)).get();
59978
+ const pageScope = and73(eq85(gaSearchLandingPages.projectId, project.id), eq85(gaSearchLandingPages.windowKey, window2));
59890
59979
  const totalRows = otherProperty ? 0 : db.select({ value: count5() }).from(gaSearchLandingPages).where(pageScope).get()?.value ?? 0;
59891
59980
  const rows = otherProperty ? [] : db.select().from(gaSearchLandingPages).where(pageScope).orderBy(desc37(gaSearchLandingPages.clicks), desc37(gaSearchLandingPages.impressions), asc21(gaSearchLandingPages.landingPage)).limit(limit).offset(offset).all();
59892
59981
  const total = stored ? toWireMetrics({
@@ -59926,7 +60015,7 @@ function searchConsoleLinkFor(db, projectId, total, windowStart, windowEnd) {
59926
60015
  const stored = db.select({
59927
60016
  days: count5(),
59928
60017
  impressions: sql36`sum(${gscDailyTotals.impressions})`
59929
- }).from(gscDailyTotals).where(and72(eq84(gscDailyTotals.projectId, projectId), gte23(gscDailyTotals.date, windowStart), lte23(gscDailyTotals.date, windowEnd))).get();
60018
+ }).from(gscDailyTotals).where(and73(eq85(gscDailyTotals.projectId, projectId), gte23(gscDailyTotals.date, windowStart), lte23(gscDailyTotals.date, windowEnd))).get();
59930
60019
  const searchConsoleImpressions = stored && stored.days > 0 ? Number(stored.impressions ?? 0) : null;
59931
60020
  if (gaSearchLandingHasSearchData(total)) return { state: "has-data", searchConsoleImpressions };
59932
60021
  if (searchConsoleImpressions === null) return { state: "unknown", searchConsoleImpressions };
@@ -59951,9 +60040,10 @@ function withSessionShares(rows) {
59951
60040
  }
59952
60041
  var DAY_MS4 = 864e5;
59953
60042
  var GA_TREND_FILLING_DAYS = 2;
60043
+ var gaFillingFrom = (now) => new Date(Date.parse(now.toISOString().slice(0, 10)) - (GA_TREND_FILLING_DAYS - 1) * DAY_MS4).toISOString().slice(0, 10);
59954
60044
  function aiReferralDailyTrend(days, measuredDates, firstAiDate, now) {
59955
60045
  if (!firstAiDate) return null;
59956
- const fillingFrom = new Date(Date.parse(now.toISOString().slice(0, 10)) - (GA_TREND_FILLING_DAYS - 1) * DAY_MS4).toISOString().slice(0, 10);
60046
+ const fillingFrom = gaFillingFrom(now);
59957
60047
  const usable = [...new Set(measuredDates)].filter((date) => date >= firstAiDate && date < fillingFrom).sort();
59958
60048
  if (usable.length < 2) return null;
59959
60049
  const sessionsByDate = new Map(days.map((day) => [day.date, day.sessions]));
@@ -59964,6 +60054,49 @@ function aiReferralDailyTrend(days, measuredDates, firstAiDate, now) {
59964
60054
  const fit = linearTrend(axis.map((date) => measured.has(date) ? sessionsByDate.get(date) ?? 0 : null));
59965
60055
  return fit ? { ...fit, startDate: axis[fit.startIndex], endDate: axis[fit.endIndex] } : null;
59966
60056
  }
60057
+ var addMonths = (month, n) => {
60058
+ const [y, m] = month.split("-").map(Number);
60059
+ return new Date(Date.UTC(y, m - 1 + n, 1)).toISOString().slice(0, 7);
60060
+ };
60061
+ var lastDayOf = (month) => new Date(Date.parse(`${addMonths(month, 1)}-01T00:00:00.000Z`) - DAY_MS4).toISOString().slice(0, 10);
60062
+ function aiReferralMonthlySeries(input) {
60063
+ const today = input.now.toISOString().slice(0, 10);
60064
+ const fillingFrom = gaFillingFrom(input.now);
60065
+ const firstDay = input.range.startDate ?? [...input.days.map((day) => day.date), ...input.measuredDates].sort()[0];
60066
+ if (!firstDay) return { months: [], monthlyTrend: null };
60067
+ const lastDay = input.range.endDate && input.range.endDate < today ? input.range.endDate : today;
60068
+ const labels = new Map(input.byEngine.series.map((s) => [s.key, s.label]));
60069
+ const measured = new Set(input.measuredDates);
60070
+ const months = [];
60071
+ for (let month = firstDay.slice(0, 7); month <= lastDay.slice(0, 7); month = addMonths(month, 1)) {
60072
+ const inMonth = (date) => date.startsWith(month);
60073
+ const monthStart = `${month}-01`;
60074
+ const monthEnd = lastDayOf(month);
60075
+ const monthDates = calendarDateRange(monthStart, monthEnd);
60076
+ const days = input.days.filter((day) => inMonth(day.date));
60077
+ const reported = days.length > 0 || monthDates.some((date) => measured.has(date));
60078
+ const engines = /* @__PURE__ */ new Map();
60079
+ for (const day of input.byEngine.days) {
60080
+ if (!inMonth(day.date)) continue;
60081
+ for (const [key, value] of Object.entries(day.values)) engines.set(key, (engines.get(key) ?? 0) + value);
60082
+ }
60083
+ months.push({
60084
+ month,
60085
+ sessions: reported ? days.reduce((sum, day) => sum + day.sessions, 0) : null,
60086
+ complete: reported && monthStart >= firstDay && monthEnd <= lastDay && monthEnd < fillingFrom && monthDates.every((date) => measured.has(date)),
60087
+ engines: [...engines].filter(([, v]) => v > 0).sort((a, b) => b[1] - a[1]).map(([key, sessions]) => ({ key, label: labels.get(key) ?? key, sessions }))
60088
+ });
60089
+ }
60090
+ const fromMonth = input.firstAiDate?.slice(0, 7);
60091
+ const fit = fromMonth ? linearTrend(months.map((m) => m.complete && m.month >= fromMonth ? m.sessions : null)) : null;
60092
+ const monthlyTrend = fit ? {
60093
+ ...fit,
60094
+ startMonth: months[fit.startIndex].month,
60095
+ endMonth: months[fit.endIndex].month,
60096
+ changePct: fit.start > 0 ? deltaPercent(fit.end, fit.start) : null
60097
+ } : null;
60098
+ return { months, monthlyTrend };
60099
+ }
59967
60100
  function dateRangeConditions(column, range) {
59968
60101
  const conditions = [];
59969
60102
  if (range.startDate) conditions.push(sql37`${column} >= ${range.startDate}`);
@@ -60025,8 +60158,8 @@ function keepWinningDimensionRows(rows, groupKey) {
60025
60158
  function persistAcquisitionMeasurement(db, input) {
60026
60159
  const { startDate, endDate } = input.report;
60027
60160
  db.transaction((tx) => {
60028
- tx.delete(gaAcquisitionDaily).where(and73(
60029
- eq85(gaAcquisitionDaily.projectId, input.projectId),
60161
+ tx.delete(gaAcquisitionDaily).where(and74(
60162
+ eq86(gaAcquisitionDaily.projectId, input.projectId),
60030
60163
  sql37`${gaAcquisitionDaily.date} >= ${startDate}`,
60031
60164
  sql37`${gaAcquisitionDaily.date} <= ${endDate}`
60032
60165
  )).run();
@@ -60067,8 +60200,8 @@ function persistAcquisitionMeasurement(db, input) {
60067
60200
  function persistLeadMeasurement(db, input) {
60068
60201
  const { startDate, endDate } = input.report;
60069
60202
  db.transaction((tx) => {
60070
- tx.delete(gaLeadEventsDaily).where(and73(
60071
- eq85(gaLeadEventsDaily.projectId, input.projectId),
60203
+ tx.delete(gaLeadEventsDaily).where(and74(
60204
+ eq86(gaLeadEventsDaily.projectId, input.projectId),
60072
60205
  sql37`${gaLeadEventsDaily.date} >= ${startDate}`,
60073
60206
  sql37`${gaLeadEventsDaily.date} <= ${endDate}`
60074
60207
  )).run();
@@ -60114,7 +60247,8 @@ function persistLeadMeasurement(db, input) {
60114
60247
  }
60115
60248
  function resetLeadMeasurement(db, projectId, updatedAt) {
60116
60249
  db.transaction((tx) => {
60117
- tx.delete(gaLeadEventsDaily).where(eq85(gaLeadEventsDaily.projectId, projectId)).run();
60250
+ tx.delete(gaLeadEventsDaily).where(eq86(gaLeadEventsDaily.projectId, projectId)).run();
60251
+ tx.delete(gaSyncCoverage).where(and74(eq86(gaSyncCoverage.projectId, projectId), eq86(gaSyncCoverage.component, "leads"))).run();
60118
60252
  tx.insert(gaMeasurementSyncStates).values({ projectId, leadStatus: "never-synced", leadError: null, leadSyncedAt: null, leadAttributionScope: null, leadEventNames: null, updatedAt }).onConflictDoUpdate({
60119
60253
  target: gaMeasurementSyncStates.projectId,
60120
60254
  set: { leadStatus: "never-synced", leadError: null, leadSyncedAt: null, leadAttributionScope: null, leadEventNames: null, updatedAt }
@@ -60456,7 +60590,7 @@ async function ga4Routes(app, opts) {
60456
60590
  if (!connected2) {
60457
60591
  return { connected: false, propertyId: null, clientEmail: null, authMethod: null, lastSyncedAt: null };
60458
60592
  }
60459
- const latestSync = app.db.select({ syncedAt: gaTrafficSummaries.syncedAt }).from(gaTrafficSummaries).where(eq85(gaTrafficSummaries.projectId, project.id)).orderBy(desc38(gaTrafficSummaries.syncedAt)).limit(1).get();
60593
+ const latestSync = app.db.select({ syncedAt: gaTrafficSummaries.syncedAt }).from(gaTrafficSummaries).where(eq86(gaTrafficSummaries.projectId, project.id)).orderBy(desc38(gaTrafficSummaries.syncedAt)).limit(1).get();
60460
60594
  return {
60461
60595
  connected: true,
60462
60596
  propertyId: saConn?.propertyId ?? oauthConn?.propertyId ?? null,
@@ -60479,7 +60613,7 @@ async function ga4Routes(app, opts) {
60479
60613
  const syncAi = !only || only === "ai";
60480
60614
  const syncSocial = !only || only === "social";
60481
60615
  const syncSearchLanding = !only || only === "search-landing";
60482
- const measurementState = app.db.select().from(gaMeasurementSyncStates).where(eq85(gaMeasurementSyncStates.projectId, project.id)).get();
60616
+ const measurementState = app.db.select().from(gaMeasurementSyncStates).where(eq86(gaMeasurementSyncStates.projectId, project.id)).get();
60483
60617
  const acquisitionDays = measurementState?.acquisitionSyncedAt != null ? days : GA4_MAX_SYNC_DAYS;
60484
60618
  let leadDays = GA4_MAX_SYNC_DAYS;
60485
60619
  let leadEventNames = [];
@@ -60516,47 +60650,72 @@ async function ga4Routes(app, opts) {
60516
60650
  let rows = [];
60517
60651
  let aiReferrals = [];
60518
60652
  let socialReferrals = [];
60519
- const WINDOW_KEYS = ["7d", "30d", "90d"];
60520
- const fetches = [
60653
+ const WINDOW_KEYS = GA_OVERVIEW_WINDOWS;
60654
+ const [summary3, windowSummaries, windowMetrics] = await Promise.all([
60521
60655
  fetchAggregateSummary(accessToken, propertyId, days),
60522
- ...WINDOW_KEYS.map((w) => fetchWindowSummary(accessToken, propertyId, w)),
60523
- fetchDailyTotals(accessToken, propertyId),
60524
- ...WINDOW_KEYS.map((w) => fetchWindowMetrics(accessToken, propertyId, w))
60525
- ];
60526
- if (syncTraffic) fetches.push(fetchTrafficByLandingPage(accessToken, propertyId, days));
60527
- if (syncAi) fetches.push(fetchAiReferrals(accessToken, propertyId, days));
60528
- if (syncSocial) fetches.push(fetchSocialReferrals(accessToken, propertyId, days));
60656
+ Promise.all(WINDOW_KEYS.map((w) => fetchWindowSummary(accessToken, propertyId, w))),
60657
+ Promise.all(WINDOW_KEYS.map((w) => fetchWindowMetrics(accessToken, propertyId, w)))
60658
+ ]);
60659
+ const requested = { startDate: summary3.periodStart, endDate: summary3.periodEnd };
60660
+ const definition2 = JSON.stringify(["v1", windowMetrics[0].timeZone ?? "UTC"]);
60661
+ const planInput = { projectId: project.id, propertyId, requested, definition: definition2 };
60662
+ const trafficPlan = planGaSyncCoverage(app.db, { ...planInput, component: "traffic" }, startedAt);
60663
+ const aiPlan = planGaSyncCoverage(app.db, { ...planInput, component: "ai" }, startedAt);
60664
+ const socialPlan = planGaSyncCoverage(app.db, { ...planInput, component: "social" }, startedAt);
60665
+ const longestWindow = windowMetrics.at(-1);
60666
+ const dailyPlan = planGaSyncCoverage(app.db, {
60667
+ ...planInput,
60668
+ component: "daily",
60669
+ requested: { startDate: longestWindow.previous.startDate, endDate: shiftIsoCalendarDate(longestWindow.current.endDate, 1) }
60670
+ }, startedAt);
60671
+ const acquisitionPlan = planGaSyncCoverage(app.db, {
60672
+ ...planInput,
60673
+ component: "acquisition",
60674
+ requested: { startDate: shiftIsoCalendarDate(requested.endDate, -acquisitionDays), endDate: requested.endDate }
60675
+ }, startedAt);
60676
+ const leadPlan = planGaSyncCoverage(app.db, {
60677
+ ...planInput,
60678
+ component: "leads",
60679
+ definition: JSON.stringify([definition2, ...leadEventNames]),
60680
+ requested: { startDate: shiftIsoCalendarDate(requested.endDate, -leadDays), endDate: requested.endDate }
60681
+ }, startedAt);
60682
+ const searchRequestedAt = /* @__PURE__ */ new Date();
60683
+ const searchTimeZone = windowMetrics[0].timeZone ?? null;
60684
+ const searchWindows = syncSearchLanding ? searchLandingWindowsToFetch(app.db, project.id, propertyId, searchTimeZone, searchRequestedAt.toISOString()) : [];
60529
60685
  const measurementFetch = Promise.allSettled([
60530
- fetchAcquisitionByChannel(accessToken, propertyId, acquisitionDays),
60531
- leadEventNames.length > 0 ? fetchLeadEvents(accessToken, propertyId, leadEventNames, leadDays) : Promise.resolve(null),
60532
- // Fails soft like the components above: its own state, never the run.
60533
- syncSearchLanding ? fetchSearchLandingPages(accessToken, propertyId) : Promise.resolve(null)
60686
+ fetchGaSyncRanges(acquisitionPlan, (range) => fetchAcquisitionByChannel(accessToken, propertyId, acquisitionDays, { dateRange: range })),
60687
+ leadEventNames.length > 0 ? fetchGaSyncRanges(leadPlan, (range) => fetchLeadEvents(accessToken, propertyId, leadEventNames, leadDays, range)) : Promise.resolve(null),
60688
+ syncSearchLanding ? searchWindows.length > 0 ? fetchSearchLandingPages(accessToken, propertyId, {
60689
+ windows: searchWindows,
60690
+ now: searchRequestedAt,
60691
+ timeZone: searchTimeZone ?? void 0
60692
+ }) : Promise.resolve({ status: "ready", windows: [] }) : Promise.resolve(null)
60534
60693
  ]);
60535
- const results = await Promise.all(fetches);
60536
- const summary3 = results[0];
60537
- const windowSummaries = results.slice(1, 1 + WINDOW_KEYS.length);
60538
- const dailyTotals = results[1 + WINDOW_KEYS.length];
60539
- const windowMetrics = results.slice(2 + WINDOW_KEYS.length, 2 + 2 * WINDOW_KEYS.length);
60540
- let idx = 2 + 2 * WINDOW_KEYS.length;
60541
- if (syncTraffic) {
60542
- rows = results[idx++];
60543
- }
60544
- if (syncAi) {
60545
- aiReferrals = results[idx++];
60546
- }
60547
- if (syncSocial) {
60548
- socialReferrals = results[idx++];
60549
- }
60694
+ const [dailyReports, trafficReports, aiReports, socialReports] = await Promise.all([
60695
+ fetchGaSyncRanges(dailyPlan, (range) => fetchDailyTotals(accessToken, propertyId, void 0, { dateRange: range })),
60696
+ fetchGaSyncRanges(trafficPlan, (range) => fetchTrafficByLandingPage(accessToken, propertyId, days, range)),
60697
+ syncAi ? fetchGaSyncRanges(aiPlan, (range) => fetchAiReferrals(accessToken, propertyId, days, range)) : Promise.resolve([]),
60698
+ syncSocial ? fetchGaSyncRanges(socialPlan, (range) => fetchSocialReferrals(accessToken, propertyId, days, range)) : Promise.resolve([])
60699
+ ]);
60700
+ rows = trafficReports.flatMap((result) => result.report);
60701
+ aiReferrals = aiReports.flatMap((result) => result.report);
60702
+ socialReferrals = socialReports.flatMap((result) => result.report);
60703
+ const dailyTotals = { rows: dailyReports.flatMap((result) => result.report.rows) };
60550
60704
  const now = (/* @__PURE__ */ new Date()).toISOString();
60705
+ if (resolveCurrentGa4PropertyId(opts, project.name, project.canonicalDomain) !== propertyId) {
60706
+ throw validationError("GA4 property changed during sync; retry against the current connection");
60707
+ }
60551
60708
  app.db.transaction((tx) => {
60552
60709
  if (syncTraffic) {
60553
- tx.delete(gaTrafficSnapshots).where(
60554
- and73(
60555
- eq85(gaTrafficSnapshots.projectId, project.id),
60556
- sql37`${gaTrafficSnapshots.date} >= ${summary3.periodStart}`,
60557
- sql37`${gaTrafficSnapshots.date} <= ${summary3.periodEnd}`
60558
- )
60559
- ).run();
60710
+ for (const { range } of trafficReports) {
60711
+ tx.delete(gaTrafficSnapshots).where(
60712
+ and74(
60713
+ eq86(gaTrafficSnapshots.projectId, project.id),
60714
+ sql37`${gaTrafficSnapshots.date} >= ${range.startDate}`,
60715
+ sql37`${gaTrafficSnapshots.date} <= ${range.endDate}`
60716
+ )
60717
+ ).run();
60718
+ }
60560
60719
  for (const row of rows) {
60561
60720
  tx.insert(gaTrafficSnapshots).values({
60562
60721
  id: crypto51.randomUUID(),
@@ -60573,13 +60732,15 @@ async function ga4Routes(app, opts) {
60573
60732
  }).run();
60574
60733
  }
60575
60734
  }
60576
- tx.delete(gaDailyTotals).where(
60577
- and73(
60578
- eq85(gaDailyTotals.projectId, project.id),
60579
- sql37`${gaDailyTotals.date} >= ${dailyTotals.startDate}`,
60580
- sql37`${gaDailyTotals.date} <= ${dailyTotals.endDate}`
60581
- )
60582
- ).run();
60735
+ for (const { report } of dailyReports) {
60736
+ tx.delete(gaDailyTotals).where(
60737
+ and74(
60738
+ eq86(gaDailyTotals.projectId, project.id),
60739
+ sql37`${gaDailyTotals.date} >= ${report.startDate}`,
60740
+ sql37`${gaDailyTotals.date} <= ${report.endDate}`
60741
+ )
60742
+ ).run();
60743
+ }
60583
60744
  for (const row of dailyTotals.rows) {
60584
60745
  tx.insert(gaDailyTotals).values({
60585
60746
  id: crypto51.randomUUID(),
@@ -60605,7 +60766,7 @@ async function ga4Routes(app, opts) {
60605
60766
  createdAt: now
60606
60767
  }).run();
60607
60768
  }
60608
- tx.delete(gaWindowMetrics).where(eq85(gaWindowMetrics.projectId, project.id)).run();
60769
+ tx.delete(gaWindowMetrics).where(eq86(gaWindowMetrics.projectId, project.id)).run();
60609
60770
  for (const windowMetric of windowMetrics) {
60610
60771
  for (const [period2, totals] of [
60611
60772
  [GaOverviewPeriods.current, windowMetric.current],
@@ -60635,13 +60796,15 @@ async function ga4Routes(app, opts) {
60635
60796
  }
60636
60797
  }
60637
60798
  if (syncAi) {
60638
- tx.delete(gaAiReferrals).where(
60639
- and73(
60640
- eq85(gaAiReferrals.projectId, project.id),
60641
- sql37`${gaAiReferrals.date} >= ${summary3.periodStart}`,
60642
- sql37`${gaAiReferrals.date} <= ${summary3.periodEnd}`
60643
- )
60644
- ).run();
60799
+ for (const { range } of aiReports) {
60800
+ tx.delete(gaAiReferrals).where(
60801
+ and74(
60802
+ eq86(gaAiReferrals.projectId, project.id),
60803
+ sql37`${gaAiReferrals.date} >= ${range.startDate}`,
60804
+ sql37`${gaAiReferrals.date} <= ${range.endDate}`
60805
+ )
60806
+ ).run();
60807
+ }
60645
60808
  for (const row of aiReferrals) {
60646
60809
  tx.insert(gaAiReferrals).values({
60647
60810
  id: crypto51.randomUUID(),
@@ -60667,13 +60830,15 @@ async function ga4Routes(app, opts) {
60667
60830
  }
60668
60831
  }
60669
60832
  if (syncSocial) {
60670
- tx.delete(gaSocialReferrals).where(
60671
- and73(
60672
- eq85(gaSocialReferrals.projectId, project.id),
60673
- sql37`${gaSocialReferrals.date} >= ${summary3.periodStart}`,
60674
- sql37`${gaSocialReferrals.date} <= ${summary3.periodEnd}`
60675
- )
60676
- ).run();
60833
+ for (const { range } of socialReports) {
60834
+ tx.delete(gaSocialReferrals).where(
60835
+ and74(
60836
+ eq86(gaSocialReferrals.projectId, project.id),
60837
+ sql37`${gaSocialReferrals.date} >= ${range.startDate}`,
60838
+ sql37`${gaSocialReferrals.date} <= ${range.endDate}`
60839
+ )
60840
+ ).run();
60841
+ }
60677
60842
  for (const row of socialReferrals) {
60678
60843
  tx.insert(gaSocialReferrals).values({
60679
60844
  id: crypto51.randomUUID(),
@@ -60690,7 +60855,7 @@ async function ga4Routes(app, opts) {
60690
60855
  }
60691
60856
  }
60692
60857
  if (syncSummary) {
60693
- tx.delete(gaTrafficSummaries).where(eq85(gaTrafficSummaries.projectId, project.id)).run();
60858
+ tx.delete(gaTrafficSummaries).where(eq86(gaTrafficSummaries.projectId, project.id)).run();
60694
60859
  tx.insert(gaTrafficSummaries).values({
60695
60860
  id: crypto51.randomUUID(),
60696
60861
  projectId: project.id,
@@ -60702,7 +60867,7 @@ async function ga4Routes(app, opts) {
60702
60867
  syncedAt: now,
60703
60868
  syncRunId: runId
60704
60869
  }).run();
60705
- tx.delete(gaTrafficWindowSummaries).where(eq85(gaTrafficWindowSummaries.projectId, project.id)).run();
60870
+ tx.delete(gaTrafficWindowSummaries).where(eq86(gaTrafficWindowSummaries.projectId, project.id)).run();
60706
60871
  for (const ws of windowSummaries) {
60707
60872
  tx.insert(gaTrafficWindowSummaries).values({
60708
60873
  id: crypto51.randomUUID(),
@@ -60719,9 +60884,16 @@ async function ga4Routes(app, opts) {
60719
60884
  }).run();
60720
60885
  }
60721
60886
  }
60887
+ saveGaSyncCoverage(tx, trafficPlan, trafficReports.map((result) => result.range), now, runId);
60888
+ saveGaSyncCoverage(tx, dailyPlan, dailyReports.map((result) => result.report), now, runId);
60889
+ if (syncAi) saveGaSyncCoverage(tx, aiPlan, aiReports.map((result) => result.range), now, runId);
60890
+ if (syncSocial) saveGaSyncCoverage(tx, socialPlan, socialReports.map((result) => result.range), now, runId);
60722
60891
  });
60723
60892
  const [acquisitionSettled, leadsSettled, searchLandingSettled] = await measurementFetch;
60724
60893
  const measurementNow = (/* @__PURE__ */ new Date()).toISOString();
60894
+ if (resolveCurrentGa4PropertyId(opts, project.name, project.canonicalDomain) !== propertyId) {
60895
+ throw validationError("GA4 property changed during sync; retry against the current connection");
60896
+ }
60725
60897
  const recordMeasurementFailure = (component, reason) => {
60726
60898
  componentFailure ??= { error: reason };
60727
60899
  const message = describeError(reason);
@@ -60748,16 +60920,21 @@ async function ga4Routes(app, opts) {
60748
60920
  return recordMeasurementFailure("acquisition", acquisitionSettled.reason);
60749
60921
  }
60750
60922
  try {
60751
- persistAcquisitionMeasurement(app.db, {
60752
- projectId: project.id,
60753
- runId,
60754
- syncedAt: measurementNow,
60755
- report: acquisitionSettled.value
60923
+ app.db.transaction((tx) => {
60924
+ for (const { report } of acquisitionSettled.value) {
60925
+ persistAcquisitionMeasurement(tx, {
60926
+ projectId: project.id,
60927
+ runId,
60928
+ syncedAt: measurementNow,
60929
+ report
60930
+ });
60931
+ }
60932
+ saveGaSyncCoverage(tx, acquisitionPlan, acquisitionSettled.value.map((result) => result.report), measurementNow, runId);
60756
60933
  });
60757
60934
  return {
60758
60935
  status: "ready",
60759
60936
  days: acquisitionDays,
60760
- rowCount: acquisitionSettled.value.rows.length
60937
+ rowCount: acquisitionSettled.value.reduce((sum, result) => sum + result.report.rows.length, 0)
60761
60938
  };
60762
60939
  } catch (error) {
60763
60940
  return recordMeasurementFailure("acquisition", error);
@@ -60772,18 +60949,23 @@ async function ga4Routes(app, opts) {
60772
60949
  return { status: "not-configured", days: 0, rowCount: 0 };
60773
60950
  }
60774
60951
  try {
60775
- persistLeadMeasurement(app.db, {
60776
- projectId: project.id,
60777
- runId,
60778
- syncedAt: measurementNow,
60779
- report: leadsSettled.value,
60780
- eventNames: leadEventNames
60952
+ app.db.transaction((tx) => {
60953
+ for (const { report } of leadsSettled.value) {
60954
+ persistLeadMeasurement(tx, {
60955
+ projectId: project.id,
60956
+ runId,
60957
+ syncedAt: measurementNow,
60958
+ report,
60959
+ eventNames: leadEventNames
60960
+ });
60961
+ }
60962
+ saveGaSyncCoverage(tx, leadPlan, leadsSettled.value.map((result) => result.report), measurementNow, runId);
60781
60963
  });
60782
60964
  return {
60783
60965
  status: "ready",
60784
60966
  days: leadDays,
60785
- rowCount: leadsSettled.value.rows.length,
60786
- attributionScope: leadsSettled.value.attributionScope,
60967
+ rowCount: leadsSettled.value.reduce((sum, result) => sum + result.report.rows.length, 0),
60968
+ attributionScope: leadsSettled.value.at(-1).report.attributionScope,
60787
60969
  eventNames: leadEventNames,
60788
60970
  keyEvents: keyEventRead
60789
60971
  };
@@ -60826,19 +61008,24 @@ async function ga4Routes(app, opts) {
60826
61008
  propertyId,
60827
61009
  runId,
60828
61010
  syncedAt: measurementNow,
60829
- windows: report.windows
61011
+ windows: report.windows,
61012
+ reuse: {
61013
+ windows: GA_SEARCH_LANDING_WINDOWS.filter((window2) => !searchWindows.includes(window2)),
61014
+ timeZone: searchTimeZone,
61015
+ requestedAt: searchRequestedAt.toISOString()
61016
+ }
60830
61017
  });
60831
61018
  } catch (error) {
60832
61019
  return recordSearchLandingFailure("error", error);
60833
61020
  }
60834
61021
  })();
60835
- app.db.update(runs).set({ status: RunStatuses.completed, finishedAt: measurementNow }).where(eq85(runs.id, runId)).run();
61022
+ app.db.update(runs).set({ status: RunStatuses.completed, finishedAt: measurementNow }).where(eq86(runs.id, runId)).run();
60836
61023
  const syncedComponents = only ? [
60837
61024
  ...syncTraffic ? ["traffic"] : [],
60838
61025
  ...syncSummary ? ["summary"] : [],
60839
61026
  ...syncAi ? ["ai"] : [],
60840
61027
  ...syncSocial ? ["social"] : [],
60841
- ...searchLandingResult?.status === "ready" ? ["search-landing"] : []
61028
+ ...searchLandingResult?.status === "ready" && searchLandingResult.windows.length > 0 ? ["search-landing"] : []
60842
61029
  ] : void 0;
60843
61030
  gaLog("info", "sync.complete", {
60844
61031
  projectId: project.id,
@@ -60878,7 +61065,7 @@ async function ga4Routes(app, opts) {
60878
61065
  } catch (e) {
60879
61066
  const msg = describeError(e);
60880
61067
  gaLog("error", "sync.fetch-failed", { projectId: project.id, runId, error: msg });
60881
- app.db.update(runs).set({ status: RunStatuses.failed, error: msg, finishedAt: (/* @__PURE__ */ new Date()).toISOString() }).where(eq85(runs.id, runId)).run();
61068
+ app.db.update(runs).set({ status: RunStatuses.failed, error: msg, finishedAt: (/* @__PURE__ */ new Date()).toISOString() }).where(eq86(runs.id, runId)).run();
60882
61069
  outcome2.report({ status: OutcomeStatuses.failed, ...ga4SyncFailure(e, credentialsResolved, opts, project) });
60883
61070
  throw e;
60884
61071
  }
@@ -60887,7 +61074,7 @@ async function ga4Routes(app, opts) {
60887
61074
  const project = resolveProject(app.db, request.params.name);
60888
61075
  requireGa4Connection(opts, project.name, project.canonicalDomain);
60889
61076
  const limit = Math.max(1, Math.min(parseInt(request.query.limit ?? "50", 10) || 50, 500));
60890
- const range = resolveDateRange(request.query);
61077
+ const range = resolveDateRange(request.query, WINDOW_SETS.gaTraffic);
60891
61078
  const windowSummaryRow = range.startDate && !range.explicitDates ? app.db.select({
60892
61079
  periodStart: gaTrafficWindowSummaries.periodStart,
60893
61080
  periodEnd: gaTrafficWindowSummaries.periodEnd,
@@ -60896,9 +61083,9 @@ async function ga4Routes(app, opts) {
60896
61083
  totalDirectSessions: gaTrafficWindowSummaries.totalDirectSessions,
60897
61084
  totalUsers: gaTrafficWindowSummaries.totalUsers
60898
61085
  }).from(gaTrafficWindowSummaries).where(
60899
- and73(
60900
- eq85(gaTrafficWindowSummaries.projectId, project.id),
60901
- eq85(gaTrafficWindowSummaries.windowKey, range.window)
61086
+ and74(
61087
+ eq86(gaTrafficWindowSummaries.projectId, project.id),
61088
+ eq86(gaTrafficWindowSummaries.windowKey, range.window)
60902
61089
  )
60903
61090
  ).get() : null;
60904
61091
  const projectSummaryRow = app.db.select({
@@ -60907,11 +61094,11 @@ async function ga4Routes(app, opts) {
60907
61094
  totalSessions: gaTrafficSummaries.totalSessions,
60908
61095
  totalOrganicSessions: gaTrafficSummaries.totalOrganicSessions,
60909
61096
  totalUsers: gaTrafficSummaries.totalUsers
60910
- }).from(gaTrafficSummaries).where(eq85(gaTrafficSummaries.projectId, project.id)).get();
61097
+ }).from(gaTrafficSummaries).where(eq86(gaTrafficSummaries.projectId, project.id)).get();
60911
61098
  const retainedBounds = [
60912
- app.db.select({ min: sql37`MIN(${gaTrafficSnapshots.date})`, max: sql37`MAX(${gaTrafficSnapshots.date})` }).from(gaTrafficSnapshots).where(eq85(gaTrafficSnapshots.projectId, project.id)).get(),
60913
- app.db.select({ min: sql37`MIN(${gaAiReferrals.date})`, max: sql37`MAX(${gaAiReferrals.date})` }).from(gaAiReferrals).where(eq85(gaAiReferrals.projectId, project.id)).get(),
60914
- app.db.select({ min: sql37`MIN(${gaSocialReferrals.date})`, max: sql37`MAX(${gaSocialReferrals.date})` }).from(gaSocialReferrals).where(eq85(gaSocialReferrals.projectId, project.id)).get()
61099
+ app.db.select({ min: sql37`MIN(${gaTrafficSnapshots.date})`, max: sql37`MAX(${gaTrafficSnapshots.date})` }).from(gaTrafficSnapshots).where(eq86(gaTrafficSnapshots.projectId, project.id)).get(),
61100
+ app.db.select({ min: sql37`MIN(${gaAiReferrals.date})`, max: sql37`MAX(${gaAiReferrals.date})` }).from(gaAiReferrals).where(eq86(gaAiReferrals.projectId, project.id)).get(),
61101
+ app.db.select({ min: sql37`MIN(${gaSocialReferrals.date})`, max: sql37`MAX(${gaSocialReferrals.date})` }).from(gaSocialReferrals).where(eq86(gaSocialReferrals.projectId, project.id)).get()
60915
61102
  ];
60916
61103
  const retainedDates = retainedBounds.flatMap((bound) => [bound?.min, bound?.max]).filter((date) => Boolean(date));
60917
61104
  const projectSummaryCoversAll = Boolean(
@@ -60920,15 +61107,15 @@ async function ga4Routes(app, opts) {
60920
61107
  const denominatorRow = windowSummaryRow ?? (range.window === "all" && projectSummaryCoversAll ? projectSummaryRow : null);
60921
61108
  const denominatorPeriod = denominatorRow?.periodStart && denominatorRow.periodEnd ? { startDate: denominatorRow.periodStart, endDate: denominatorRow.periodEnd } : null;
60922
61109
  const measuredRange = denominatorPeriod ? { ...range, ...denominatorPeriod } : range;
60923
- const snapshotConditions = [eq85(gaTrafficSnapshots.projectId, project.id), ...dateRangeConditions(gaTrafficSnapshots.date, measuredRange)];
60924
- const aiConditions = [eq85(gaAiReferrals.projectId, project.id), ...dateRangeConditions(gaAiReferrals.date, measuredRange)];
60925
- const socialConditions = [eq85(gaSocialReferrals.projectId, project.id), ...dateRangeConditions(gaSocialReferrals.date, measuredRange)];
61110
+ const snapshotConditions = [eq86(gaTrafficSnapshots.projectId, project.id), ...dateRangeConditions(gaTrafficSnapshots.date, measuredRange)];
61111
+ const aiConditions = [eq86(gaAiReferrals.projectId, project.id), ...dateRangeConditions(gaAiReferrals.date, measuredRange)];
61112
+ const socialConditions = [eq86(gaSocialReferrals.projectId, project.id), ...dateRangeConditions(gaSocialReferrals.date, measuredRange)];
60926
61113
  const snapshotTotalsRow = !denominatorRow ? (() => {
60927
61114
  const summed = app.db.select({
60928
61115
  totalSessions: sql37`COALESCE(SUM(${gaTrafficSnapshots.sessions}), 0)`,
60929
61116
  totalOrganicSessions: sql37`COALESCE(SUM(${gaTrafficSnapshots.organicSessions}), 0)`,
60930
61117
  totalUsers: sql37`COALESCE(SUM(${gaTrafficSnapshots.users}), 0)`
60931
- }).from(gaTrafficSnapshots).where(and73(...snapshotConditions)).get();
61118
+ }).from(gaTrafficSnapshots).where(and74(...snapshotConditions)).get();
60932
61119
  return {
60933
61120
  totalSessions: summed?.totalSessions ?? 0,
60934
61121
  totalOrganicSessions: summed?.totalOrganicSessions ?? 0,
@@ -60941,21 +61128,21 @@ async function ga4Routes(app, opts) {
60941
61128
  const summaryRow = denominatorRow ?? snapshotTotalsRow;
60942
61129
  const directTotalRow = windowSummaryRow ? { totalDirectSessions: windowSummaryRow.totalDirectSessions } : app.db.select({
60943
61130
  totalDirectSessions: sql37`COALESCE(SUM(${gaTrafficSnapshots.directSessions}), 0)`
60944
- }).from(gaTrafficSnapshots).where(and73(...snapshotConditions)).get();
61131
+ }).from(gaTrafficSnapshots).where(and74(...snapshotConditions)).get();
60945
61132
  const rows = app.db.select({
60946
61133
  landingPage: sql37`COALESCE(${gaTrafficSnapshots.landingPageNormalized}, ${gaTrafficSnapshots.landingPage})`,
60947
61134
  sessions: sql37`SUM(${gaTrafficSnapshots.sessions})`,
60948
61135
  organicSessions: sql37`SUM(${gaTrafficSnapshots.organicSessions})`,
60949
61136
  directSessions: sql37`COALESCE(SUM(${gaTrafficSnapshots.directSessions}), 0)`,
60950
61137
  users: sql37`SUM(${gaTrafficSnapshots.users})`
60951
- }).from(gaTrafficSnapshots).where(and73(...snapshotConditions)).groupBy(sql37`COALESCE(${gaTrafficSnapshots.landingPageNormalized}, ${gaTrafficSnapshots.landingPage})`).orderBy(sql37`SUM(${gaTrafficSnapshots.sessions}) DESC`).limit(limit).all();
61138
+ }).from(gaTrafficSnapshots).where(and74(...snapshotConditions)).groupBy(sql37`COALESCE(${gaTrafficSnapshots.landingPageNormalized}, ${gaTrafficSnapshots.landingPage})`).orderBy(sql37`SUM(${gaTrafficSnapshots.sessions}) DESC`).limit(limit).all();
60952
61139
  const aiReferralRows = app.db.select({
60953
61140
  source: gaAiReferrals.source,
60954
61141
  medium: gaAiReferrals.medium,
60955
61142
  trafficClass: gaAiReferrals.trafficClass,
60956
61143
  sourceDimension: gaAiReferrals.sourceDimension,
60957
61144
  sessions: sql37`SUM(${gaAiReferrals.sessions})`
60958
- }).from(gaAiReferrals).where(and73(...aiConditions)).groupBy(gaAiReferrals.source, gaAiReferrals.medium, gaAiReferrals.trafficClass, gaAiReferrals.sourceDimension).all();
61145
+ }).from(gaAiReferrals).where(and74(...aiConditions)).groupBy(gaAiReferrals.source, gaAiReferrals.medium, gaAiReferrals.trafficClass, gaAiReferrals.sourceDimension).all();
60959
61146
  const aiReferralLandingPageRows = app.db.select({
60960
61147
  source: gaAiReferrals.source,
60961
61148
  medium: gaAiReferrals.medium,
@@ -60963,7 +61150,7 @@ async function ga4Routes(app, opts) {
60963
61150
  sourceDimension: gaAiReferrals.sourceDimension,
60964
61151
  landingPage: sql37`COALESCE(${gaAiReferrals.landingPageNormalized}, ${gaAiReferrals.landingPage})`,
60965
61152
  sessions: sql37`SUM(${gaAiReferrals.sessions})`
60966
- }).from(gaAiReferrals).where(and73(...aiConditions)).groupBy(
61153
+ }).from(gaAiReferrals).where(and74(...aiConditions)).groupBy(
60967
61154
  gaAiReferrals.source,
60968
61155
  gaAiReferrals.medium,
60969
61156
  gaAiReferrals.trafficClass,
@@ -60978,7 +61165,7 @@ async function ga4Routes(app, opts) {
60978
61165
  sourceDimension: gaAiReferrals.sourceDimension,
60979
61166
  channelGroup: gaAiReferrals.channelGroup,
60980
61167
  sessions: gaAiReferrals.sessions
60981
- }).from(gaAiReferrals).where(and73(...aiConditions)).all();
61168
+ }).from(gaAiReferrals).where(and74(...aiConditions)).all();
60982
61169
  const aiReferrals = keepWinningDimensionRows(aiReferralRows, (r) => r.source);
60983
61170
  const aiReferralLandingPages = keepWinningDimensionRows(
60984
61171
  aiReferralLandingPageRows,
@@ -60995,16 +61182,16 @@ async function ga4Routes(app, opts) {
60995
61182
  // dates counts a returning visitor once per day they returned. Same
60996
61183
  // conclusion the AI-referral surfaces reached in 4.135.0.
60997
61184
  sessions: sql37`SUM(${gaSocialReferrals.sessions})`
60998
- }).from(gaSocialReferrals).where(and73(...socialConditions)).groupBy(gaSocialReferrals.source, gaSocialReferrals.medium, gaSocialReferrals.channelGroup).orderBy(sql37`SUM(${gaSocialReferrals.sessions}) DESC`).all();
61185
+ }).from(gaSocialReferrals).where(and74(...socialConditions)).groupBy(gaSocialReferrals.source, gaSocialReferrals.medium, gaSocialReferrals.channelGroup).orderBy(sql37`SUM(${gaSocialReferrals.sessions}) DESC`).all();
60999
61186
  const socialDailyRows = app.db.select({
61000
61187
  date: gaSocialReferrals.date,
61001
61188
  source: gaSocialReferrals.source,
61002
61189
  sessions: sql37`SUM(${gaSocialReferrals.sessions})`
61003
- }).from(gaSocialReferrals).where(and73(...socialConditions)).groupBy(gaSocialReferrals.date, gaSocialReferrals.source).all();
61190
+ }).from(gaSocialReferrals).where(and74(...socialConditions)).groupBy(gaSocialReferrals.date, gaSocialReferrals.source).all();
61004
61191
  const socialTotals = app.db.select({
61005
61192
  sessions: sql37`SUM(${gaSocialReferrals.sessions})`
61006
- }).from(gaSocialReferrals).where(and73(...socialConditions)).get();
61007
- const latestSync = app.db.select({ syncedAt: gaTrafficSummaries.syncedAt }).from(gaTrafficSummaries).where(eq85(gaTrafficSummaries.projectId, project.id)).orderBy(desc38(gaTrafficSummaries.syncedAt)).limit(1).get();
61193
+ }).from(gaSocialReferrals).where(and74(...socialConditions)).get();
61194
+ const latestSync = app.db.select({ syncedAt: gaTrafficSummaries.syncedAt }).from(gaTrafficSummaries).where(eq86(gaTrafficSummaries.projectId, project.id)).orderBy(desc38(gaTrafficSummaries.syncedAt)).limit(1).get();
61008
61195
  const total = summaryRow?.totalSessions ?? 0;
61009
61196
  const totalDirectSessions = directTotalRow?.totalDirectSessions ?? 0;
61010
61197
  const totalOrganicSessions = summaryRow?.totalOrganicSessions ?? 0;
@@ -61055,9 +61242,8 @@ async function ga4Routes(app, opts) {
61055
61242
  // every date in the window) counted one visitor once per row. Withdrawn
61056
61243
  // in 4.135.0; see `ga-ai-referral-aggregation.ts` for why no correct
61057
61244
  // figure can be computed or fetched.
61058
- // Shares of the rows' own sum, not of `aiSessionsDeduped`: that total
61059
- // keeps the winning lens per day, these rows one lens per source over the
61060
- // window, so it can exceed what the rows add up to.
61245
+ // Shares of the rows' own sum, which agrees with `aiSessionsDeduped`:
61246
+ // both count only the session lens across the same window.
61061
61247
  aiReferrals: aiReferralDtos,
61062
61248
  aiEngines: groupAiEngines(aiReferralDtos),
61063
61249
  aiReferralLandingPages: aiReferralLandingPages.map((r) => ({
@@ -61126,8 +61312,8 @@ async function ga4Routes(app, opts) {
61126
61312
  app.get("/projects/:name/ga/ai-referral-history", async (request, _reply) => {
61127
61313
  const project = resolveProject(app.db, request.params.name);
61128
61314
  requireGa4Connection(opts, project.name, project.canonicalDomain);
61129
- const range = resolveDateRange(request.query);
61130
- const conditions = [eq85(gaAiReferrals.projectId, project.id), ...dateRangeConditions(gaAiReferrals.date, range)];
61315
+ const range = resolveDateRange(request.query, WINDOW_SETS.gaTraffic);
61316
+ const conditions = [eq86(gaAiReferrals.projectId, project.id), ...dateRangeConditions(gaAiReferrals.date, range)];
61131
61317
  const rows = app.db.select({
61132
61318
  date: gaAiReferrals.date,
61133
61319
  source: gaAiReferrals.source,
@@ -61140,7 +61326,7 @@ async function ga4Routes(app, opts) {
61140
61326
  // added up. ga4AiReferralDtoSchema.users was deprecated for this in
61141
61327
  // 4.135.0; the history endpoint kept emitting one.
61142
61328
  sessions: sql37`SUM(${gaAiReferrals.sessions})`
61143
- }).from(gaAiReferrals).where(and73(...conditions)).groupBy(
61329
+ }).from(gaAiReferrals).where(and74(...conditions)).groupBy(
61144
61330
  gaAiReferrals.date,
61145
61331
  gaAiReferrals.source,
61146
61332
  gaAiReferrals.medium,
@@ -61153,8 +61339,8 @@ async function ga4Routes(app, opts) {
61153
61339
  app.get("/projects/:name/ga/ai-referral-daily", async (request, _reply) => {
61154
61340
  const project = resolveProject(app.db, request.params.name);
61155
61341
  requireGa4Connection(opts, project.name, project.canonicalDomain);
61156
- const range = resolveDateRange(request.query);
61157
- const conditions = [eq85(gaAiReferrals.projectId, project.id), ...dateRangeConditions(gaAiReferrals.date, range)];
61342
+ const range = resolveDateRange(request.query, WINDOW_SETS.gaTraffic);
61343
+ const conditions = [eq86(gaAiReferrals.projectId, project.id), ...dateRangeConditions(gaAiReferrals.date, range)];
61158
61344
  const rows = app.db.select({
61159
61345
  date: gaAiReferrals.date,
61160
61346
  source: gaAiReferrals.source,
@@ -61163,17 +61349,22 @@ async function ga4Routes(app, opts) {
61163
61349
  sourceDimension: gaAiReferrals.sourceDimension,
61164
61350
  channelGroup: gaAiReferrals.channelGroup,
61165
61351
  sessions: gaAiReferrals.sessions
61166
- }).from(gaAiReferrals).where(and73(...conditions)).all();
61352
+ }).from(gaAiReferrals).where(and74(...conditions)).all();
61167
61353
  const series = buildAiReferralDailySeries(rows);
61168
- const measuredDates = app.db.selectDistinct({ date: gaDailyTotals.date }).from(gaDailyTotals).where(and73(eq85(gaDailyTotals.projectId, project.id), ...dateRangeConditions(gaDailyTotals.date, range))).all().map((row) => row.date);
61169
- const firstAiDate = app.db.select({ date: sql37`MIN(${gaAiReferrals.date})` }).from(gaAiReferrals).where(eq85(gaAiReferrals.projectId, project.id)).get()?.date ?? null;
61170
- return { ...series, trend: aiReferralDailyTrend(series.days, measuredDates, firstAiDate, /* @__PURE__ */ new Date()) };
61354
+ const measuredDates = app.db.selectDistinct({ date: gaDailyTotals.date }).from(gaDailyTotals).where(and74(eq86(gaDailyTotals.projectId, project.id), ...dateRangeConditions(gaDailyTotals.date, range))).all().map((row) => row.date);
61355
+ const firstAiDate = app.db.select({ date: sql37`MIN(${gaAiReferrals.date})` }).from(gaAiReferrals).where(eq86(gaAiReferrals.projectId, project.id)).get()?.date ?? null;
61356
+ const now = /* @__PURE__ */ new Date();
61357
+ return {
61358
+ ...series,
61359
+ trend: aiReferralDailyTrend(series.days, measuredDates, firstAiDate, now),
61360
+ ...aiReferralMonthlySeries({ days: series.days, byEngine: series.byEngine, measuredDates, range, firstAiDate, now })
61361
+ };
61171
61362
  });
61172
61363
  app.get("/projects/:name/ga/social-referral-history", async (request, _reply) => {
61173
61364
  const project = resolveProject(app.db, request.params.name);
61174
61365
  requireGa4Connection(opts, project.name, project.canonicalDomain);
61175
- const range = resolveDateRange(request.query);
61176
- const conditions = [eq85(gaSocialReferrals.projectId, project.id), ...dateRangeConditions(gaSocialReferrals.date, range)];
61366
+ const range = resolveDateRange(request.query, WINDOW_SETS.gaTraffic);
61367
+ const conditions = [eq86(gaSocialReferrals.projectId, project.id), ...dateRangeConditions(gaSocialReferrals.date, range)];
61177
61368
  const rows = app.db.select({
61178
61369
  date: gaSocialReferrals.date,
61179
61370
  source: gaSocialReferrals.source,
@@ -61181,7 +61372,7 @@ async function ga4Routes(app, opts) {
61181
61372
  channelGroup: gaSocialReferrals.channelGroup,
61182
61373
  sessions: gaSocialReferrals.sessions,
61183
61374
  users: gaSocialReferrals.users
61184
- }).from(gaSocialReferrals).where(and73(...conditions)).orderBy(gaSocialReferrals.date).all();
61375
+ }).from(gaSocialReferrals).where(and74(...conditions)).orderBy(gaSocialReferrals.date).all();
61185
61376
  return rows;
61186
61377
  });
61187
61378
  app.get("/projects/:name/ga/social-referral-trend", async (request, _reply) => {
@@ -61194,8 +61385,8 @@ async function ga4Routes(app, opts) {
61194
61385
  d.setDate(d.getDate() - n);
61195
61386
  return fmt(d);
61196
61387
  };
61197
- const sumSocial = (from, to) => app.db.select({ sessions: sql37`COALESCE(SUM(${gaSocialReferrals.sessions}), 0)` }).from(gaSocialReferrals).where(and73(
61198
- eq85(gaSocialReferrals.projectId, project.id),
61388
+ const sumSocial = (from, to) => app.db.select({ sessions: sql37`COALESCE(SUM(${gaSocialReferrals.sessions}), 0)` }).from(gaSocialReferrals).where(and74(
61389
+ eq86(gaSocialReferrals.projectId, project.id),
61199
61390
  sql37`${gaSocialReferrals.date} >= ${from}`,
61200
61391
  sql37`${gaSocialReferrals.date} < ${to}`
61201
61392
  )).get();
@@ -61206,16 +61397,16 @@ async function ga4Routes(app, opts) {
61206
61397
  const sourceCurrent = app.db.select({
61207
61398
  source: gaSocialReferrals.source,
61208
61399
  sessions: sql37`SUM(${gaSocialReferrals.sessions})`
61209
- }).from(gaSocialReferrals).where(and73(
61210
- eq85(gaSocialReferrals.projectId, project.id),
61400
+ }).from(gaSocialReferrals).where(and74(
61401
+ eq86(gaSocialReferrals.projectId, project.id),
61211
61402
  sql37`${gaSocialReferrals.date} >= ${daysAgo(7)}`,
61212
61403
  sql37`${gaSocialReferrals.date} < ${fmt(today)}`
61213
61404
  )).groupBy(gaSocialReferrals.source).all();
61214
61405
  const sourcePrev = app.db.select({
61215
61406
  source: gaSocialReferrals.source,
61216
61407
  sessions: sql37`SUM(${gaSocialReferrals.sessions})`
61217
- }).from(gaSocialReferrals).where(and73(
61218
- eq85(gaSocialReferrals.projectId, project.id),
61408
+ }).from(gaSocialReferrals).where(and74(
61409
+ eq86(gaSocialReferrals.projectId, project.id),
61219
61410
  sql37`${gaSocialReferrals.date} >= ${daysAgo(14)}`,
61220
61411
  sql37`${gaSocialReferrals.date} < ${daysAgo(7)}`
61221
61412
  )).groupBy(gaSocialReferrals.source).all();
@@ -61240,16 +61431,16 @@ async function ga4Routes(app, opts) {
61240
61431
  d.setDate(d.getDate() - n);
61241
61432
  return fmt(d);
61242
61433
  };
61243
- const sumTotal = (from, to) => app.db.select({ sessions: sql37`COALESCE(SUM(${gaTrafficSnapshots.sessions}), 0)` }).from(gaTrafficSnapshots).where(and73(eq85(gaTrafficSnapshots.projectId, project.id), sql37`${gaTrafficSnapshots.date} >= ${from}`, sql37`${gaTrafficSnapshots.date} < ${to}`)).get();
61244
- const sumOrganic = (from, to) => app.db.select({ sessions: sql37`COALESCE(SUM(${gaTrafficSnapshots.organicSessions}), 0)` }).from(gaTrafficSnapshots).where(and73(eq85(gaTrafficSnapshots.projectId, project.id), sql37`${gaTrafficSnapshots.date} >= ${from}`, sql37`${gaTrafficSnapshots.date} < ${to}`)).get();
61245
- const sumDirect = (from, to) => app.db.select({ sessions: sql37`COALESCE(SUM(${gaTrafficSnapshots.directSessions}), 0)` }).from(gaTrafficSnapshots).where(and73(eq85(gaTrafficSnapshots.projectId, project.id), sql37`${gaTrafficSnapshots.date} >= ${from}`, sql37`${gaTrafficSnapshots.date} < ${to}`)).get();
61246
- const sumAi = (from, to) => app.db.select({ sessions: sql37`COALESCE(SUM(${gaAiReferrals.sessions}), 0)` }).from(gaAiReferrals).where(and73(
61247
- eq85(gaAiReferrals.projectId, project.id),
61434
+ const sumTotal = (from, to) => app.db.select({ sessions: sql37`COALESCE(SUM(${gaTrafficSnapshots.sessions}), 0)` }).from(gaTrafficSnapshots).where(and74(eq86(gaTrafficSnapshots.projectId, project.id), sql37`${gaTrafficSnapshots.date} >= ${from}`, sql37`${gaTrafficSnapshots.date} < ${to}`)).get();
61435
+ const sumOrganic = (from, to) => app.db.select({ sessions: sql37`COALESCE(SUM(${gaTrafficSnapshots.organicSessions}), 0)` }).from(gaTrafficSnapshots).where(and74(eq86(gaTrafficSnapshots.projectId, project.id), sql37`${gaTrafficSnapshots.date} >= ${from}`, sql37`${gaTrafficSnapshots.date} < ${to}`)).get();
61436
+ const sumDirect = (from, to) => app.db.select({ sessions: sql37`COALESCE(SUM(${gaTrafficSnapshots.directSessions}), 0)` }).from(gaTrafficSnapshots).where(and74(eq86(gaTrafficSnapshots.projectId, project.id), sql37`${gaTrafficSnapshots.date} >= ${from}`, sql37`${gaTrafficSnapshots.date} < ${to}`)).get();
61437
+ const sumAi = (from, to) => app.db.select({ sessions: sql37`COALESCE(SUM(${gaAiReferrals.sessions}), 0)` }).from(gaAiReferrals).where(and74(
61438
+ eq86(gaAiReferrals.projectId, project.id),
61248
61439
  sql37`${gaAiReferrals.date} >= ${from}`,
61249
61440
  sql37`${gaAiReferrals.date} < ${to}`,
61250
- eq85(gaAiReferrals.sourceDimension, "session")
61441
+ eq86(gaAiReferrals.sourceDimension, "session")
61251
61442
  )).get();
61252
- const sumSocial = (from, to) => app.db.select({ sessions: sql37`COALESCE(SUM(${gaSocialReferrals.sessions}), 0)` }).from(gaSocialReferrals).where(and73(eq85(gaSocialReferrals.projectId, project.id), sql37`${gaSocialReferrals.date} >= ${from}`, sql37`${gaSocialReferrals.date} < ${to}`)).get();
61443
+ const sumSocial = (from, to) => app.db.select({ sessions: sql37`COALESCE(SUM(${gaSocialReferrals.sessions}), 0)` }).from(gaSocialReferrals).where(and74(eq86(gaSocialReferrals.projectId, project.id), sql37`${gaSocialReferrals.date} >= ${from}`, sql37`${gaSocialReferrals.date} < ${to}`)).get();
61253
61444
  const todayStr = fmt(today);
61254
61445
  const buildTrend = (sum) => {
61255
61446
  const c7 = sum(daysAgo(7), todayStr)?.sessions ?? 0;
@@ -61258,20 +61449,20 @@ async function ga4Routes(app, opts) {
61258
61449
  const p30 = sum(daysAgo(60), daysAgo(30))?.sessions ?? 0;
61259
61450
  return { sessions7d: c7, sessionsPrev7d: p7, trend7dPct: deltaPercent(c7, p7), sessions30d: c30, sessionsPrev30d: p30, trend30dPct: deltaPercent(c30, p30) };
61260
61451
  };
61261
- const aiSourceCurrent = app.db.select({ source: gaAiReferrals.source, sessions: sql37`COALESCE(SUM(${gaAiReferrals.sessions}), 0)` }).from(gaAiReferrals).where(and73(
61262
- eq85(gaAiReferrals.projectId, project.id),
61452
+ const aiSourceCurrent = app.db.select({ source: gaAiReferrals.source, sessions: sql37`COALESCE(SUM(${gaAiReferrals.sessions}), 0)` }).from(gaAiReferrals).where(and74(
61453
+ eq86(gaAiReferrals.projectId, project.id),
61263
61454
  sql37`${gaAiReferrals.date} >= ${daysAgo(7)}`,
61264
61455
  sql37`${gaAiReferrals.date} < ${todayStr}`,
61265
- eq85(gaAiReferrals.sourceDimension, "session")
61456
+ eq86(gaAiReferrals.sourceDimension, "session")
61266
61457
  )).groupBy(gaAiReferrals.source).all();
61267
- const aiSourcePrev = app.db.select({ source: gaAiReferrals.source, sessions: sql37`COALESCE(SUM(${gaAiReferrals.sessions}), 0)` }).from(gaAiReferrals).where(and73(
61268
- eq85(gaAiReferrals.projectId, project.id),
61458
+ const aiSourcePrev = app.db.select({ source: gaAiReferrals.source, sessions: sql37`COALESCE(SUM(${gaAiReferrals.sessions}), 0)` }).from(gaAiReferrals).where(and74(
61459
+ eq86(gaAiReferrals.projectId, project.id),
61269
61460
  sql37`${gaAiReferrals.date} >= ${daysAgo(14)}`,
61270
61461
  sql37`${gaAiReferrals.date} < ${daysAgo(7)}`,
61271
- eq85(gaAiReferrals.sourceDimension, "session")
61462
+ eq86(gaAiReferrals.sourceDimension, "session")
61272
61463
  )).groupBy(gaAiReferrals.source).all();
61273
- const socialSourceCurrent = app.db.select({ source: gaSocialReferrals.source, sessions: sql37`SUM(${gaSocialReferrals.sessions})` }).from(gaSocialReferrals).where(and73(eq85(gaSocialReferrals.projectId, project.id), sql37`${gaSocialReferrals.date} >= ${daysAgo(7)}`, sql37`${gaSocialReferrals.date} < ${todayStr}`)).groupBy(gaSocialReferrals.source).all();
61274
- const socialSourcePrev = app.db.select({ source: gaSocialReferrals.source, sessions: sql37`SUM(${gaSocialReferrals.sessions})` }).from(gaSocialReferrals).where(and73(eq85(gaSocialReferrals.projectId, project.id), sql37`${gaSocialReferrals.date} >= ${daysAgo(14)}`, sql37`${gaSocialReferrals.date} < ${daysAgo(7)}`)).groupBy(gaSocialReferrals.source).all();
61464
+ const socialSourceCurrent = app.db.select({ source: gaSocialReferrals.source, sessions: sql37`SUM(${gaSocialReferrals.sessions})` }).from(gaSocialReferrals).where(and74(eq86(gaSocialReferrals.projectId, project.id), sql37`${gaSocialReferrals.date} >= ${daysAgo(7)}`, sql37`${gaSocialReferrals.date} < ${todayStr}`)).groupBy(gaSocialReferrals.source).all();
61465
+ const socialSourcePrev = app.db.select({ source: gaSocialReferrals.source, sessions: sql37`SUM(${gaSocialReferrals.sessions})` }).from(gaSocialReferrals).where(and74(eq86(gaSocialReferrals.projectId, project.id), sql37`${gaSocialReferrals.date} >= ${daysAgo(14)}`, sql37`${gaSocialReferrals.date} < ${daysAgo(7)}`)).groupBy(gaSocialReferrals.source).all();
61275
61466
  const response = {
61276
61467
  total: buildTrend(sumTotal),
61277
61468
  organic: buildTrend(sumOrganic),
@@ -61287,14 +61478,14 @@ async function ga4Routes(app, opts) {
61287
61478
  const project = resolveProject(app.db, request.params.name);
61288
61479
  requireGa4Connection(opts, project.name, project.canonicalDomain);
61289
61480
  const window2 = parseGaOverviewWindow(request.query.window);
61290
- const periodRows = app.db.select().from(gaWindowMetrics).where(and73(eq85(gaWindowMetrics.projectId, project.id), eq85(gaWindowMetrics.windowKey, window2))).all();
61481
+ const periodRows = app.db.select().from(gaWindowMetrics).where(and74(eq86(gaWindowMetrics.projectId, project.id), eq86(gaWindowMetrics.windowKey, window2))).all();
61291
61482
  const current = periodRows.find((row) => row.period === GaOverviewPeriods.current) ?? null;
61292
61483
  const previous = periodRows.find((row) => row.period === GaOverviewPeriods.previous) ?? null;
61293
61484
  const fallback = gaOverviewComparisonRanges(window2);
61294
61485
  const from = previous?.startDate ?? fallback.previous.startDate;
61295
61486
  const to = current?.endDate ?? fallback.current.endDate;
61296
- const daily = app.db.select().from(gaDailyTotals).where(and73(
61297
- eq85(gaDailyTotals.projectId, project.id),
61487
+ const daily = app.db.select().from(gaDailyTotals).where(and74(
61488
+ eq86(gaDailyTotals.projectId, project.id),
61298
61489
  sql37`${gaDailyTotals.date} >= ${from}`,
61299
61490
  sql37`${gaDailyTotals.date} <= ${to}`
61300
61491
  )).all();
@@ -61303,16 +61494,16 @@ async function ga4Routes(app, opts) {
61303
61494
  app.get("/projects/:name/ga/session-history", async (request, _reply) => {
61304
61495
  const project = resolveProject(app.db, request.params.name);
61305
61496
  requireGa4Connection(opts, project.name, project.canonicalDomain);
61306
- const range = resolveDateRange(request.query);
61307
- const conditions = [eq85(gaTrafficSnapshots.projectId, project.id), ...dateRangeConditions(gaTrafficSnapshots.date, range)];
61497
+ const range = resolveDateRange(request.query, WINDOW_SETS.gaTraffic);
61498
+ const conditions = [eq86(gaTrafficSnapshots.projectId, project.id), ...dateRangeConditions(gaTrafficSnapshots.date, range)];
61308
61499
  const rows = app.db.select({
61309
61500
  date: gaTrafficSnapshots.date,
61310
61501
  sessions: sql37`SUM(${gaTrafficSnapshots.sessions})`,
61311
61502
  organicSessions: sql37`SUM(${gaTrafficSnapshots.organicSessions})`,
61312
61503
  users: sql37`SUM(${gaTrafficSnapshots.users})`
61313
- }).from(gaTrafficSnapshots).where(and73(...conditions)).groupBy(gaTrafficSnapshots.date).orderBy(gaTrafficSnapshots.date).all();
61314
- const totalConditions = [eq85(gaDailyTotals.projectId, project.id), ...dateRangeConditions(gaDailyTotals.date, range)];
61315
- const totals = app.db.select({ date: gaDailyTotals.date, users: gaDailyTotals.users }).from(gaDailyTotals).where(and73(...totalConditions)).all();
61504
+ }).from(gaTrafficSnapshots).where(and74(...conditions)).groupBy(gaTrafficSnapshots.date).orderBy(gaTrafficSnapshots.date).all();
61505
+ const totalConditions = [eq86(gaDailyTotals.projectId, project.id), ...dateRangeConditions(gaDailyTotals.date, range)];
61506
+ const totals = app.db.select({ date: gaDailyTotals.date, users: gaDailyTotals.users }).from(gaDailyTotals).where(and74(...totalConditions)).all();
61316
61507
  return buildSessionHistory(
61317
61508
  rows.map((r) => ({
61318
61509
  date: r.date,
@@ -61331,7 +61522,7 @@ async function ga4Routes(app, opts) {
61331
61522
  sessions: sql37`SUM(${gaTrafficSnapshots.sessions})`,
61332
61523
  organicSessions: sql37`SUM(${gaTrafficSnapshots.organicSessions})`,
61333
61524
  users: sql37`SUM(${gaTrafficSnapshots.users})`
61334
- }).from(gaTrafficSnapshots).where(eq85(gaTrafficSnapshots.projectId, project.id)).groupBy(sql37`COALESCE(${gaTrafficSnapshots.landingPageNormalized}, ${gaTrafficSnapshots.landingPage})`).orderBy(sql37`SUM(${gaTrafficSnapshots.sessions}) DESC`).all();
61525
+ }).from(gaTrafficSnapshots).where(eq86(gaTrafficSnapshots.projectId, project.id)).groupBy(sql37`COALESCE(${gaTrafficSnapshots.landingPageNormalized}, ${gaTrafficSnapshots.landingPage})`).orderBy(sql37`SUM(${gaTrafficSnapshots.sessions}) DESC`).all();
61335
61526
  return {
61336
61527
  pages: trafficPages.map((r) => ({
61337
61528
  landingPage: r.landingPage,
@@ -61344,7 +61535,7 @@ async function ga4Routes(app, opts) {
61344
61535
  }
61345
61536
 
61346
61537
  // ../api-routes/src/ga-acquisition.ts
61347
- import { and as and74, asc as asc22, count as count6, desc as desc39, eq as eq86, gte as gte24, lte as lte24, sql as sql38 } from "drizzle-orm";
61538
+ import { and as and75, asc as asc22, count as count6, desc as desc39, eq as eq87, gte as gte24, lte as lte24, sql as sql38 } from "drizzle-orm";
61348
61539
  async function gaAcquisitionRoutes(app) {
61349
61540
  app.get("/projects/:name/ga/acquisition", async (request) => {
61350
61541
  const project = resolveProject(app.db, request.params.name);
@@ -61367,14 +61558,14 @@ async function gaAcquisitionRoutes(app) {
61367
61558
  hostName: gaAcquisitionDaily.hostName,
61368
61559
  landingPage
61369
61560
  };
61370
- const windowFilters = [eq86(gaAcquisitionDaily.projectId, project.id)];
61561
+ const windowFilters = [eq87(gaAcquisitionDaily.projectId, project.id)];
61371
61562
  if (range.startDate) windowFilters.push(gte24(gaAcquisitionDaily.date, range.startDate));
61372
61563
  if (range.endDate) windowFilters.push(lte24(gaAcquisitionDaily.date, range.endDate));
61373
61564
  const filters = [...windowFilters];
61374
61565
  if (query2.channel !== void 0) filters.push(sql38`lower(${gaAcquisitionDaily.channelGroup}) = lower(${query2.channel})`);
61375
61566
  if (query2.hostName !== void 0) filters.push(sql38`lower(${gaAcquisitionDaily.hostName}) = lower(${query2.hostName})`);
61376
- if (query2.source !== void 0) filters.push(eq86(gaAcquisitionDaily.source, query2.source));
61377
- if (query2.medium !== void 0) filters.push(eq86(gaAcquisitionDaily.medium, query2.medium));
61567
+ if (query2.source !== void 0) filters.push(eq87(gaAcquisitionDaily.source, query2.source));
61568
+ if (query2.medium !== void 0) filters.push(eq87(gaAcquisitionDaily.medium, query2.medium));
61378
61569
  const selectedLandingPage = query2.landingPage === void 0 ? void 0 : normalizeUrlPath(query2.landingPage) ?? query2.landingPage;
61379
61570
  if (selectedLandingPage !== void 0) filters.push(sql38`${landingPage} = ${selectedLandingPage}`);
61380
61571
  const pathPrefix = normalizePathPrefix(query2.pathPrefix);
@@ -61382,7 +61573,7 @@ async function gaAcquisitionRoutes(app) {
61382
61573
  const pathname = sql38`case when instr(${landingPage}, '?') = 0 then ${landingPage} else substr(${landingPage}, 1, instr(${landingPage}, '?') - 1) end`;
61383
61574
  filters.push(literalPathPrefixCondition(pathname, pathPrefix));
61384
61575
  }
61385
- const where = and74(...filters);
61576
+ const where = and75(...filters);
61386
61577
  const sessions = sql38`coalesce(sum(${gaAcquisitionDaily.sessions}), 0)`.mapWith(Number);
61387
61578
  const selected = Object.fromEntries(groupBy.map((key) => [key, dimensions[key]]));
61388
61579
  const grouped = app.db.select({ ...selected, sessions }).from(gaAcquisitionDaily).where(where).groupBy(...groupBy.map((key) => dimensions[key]));
@@ -61394,9 +61585,9 @@ async function gaAcquisitionRoutes(app) {
61394
61585
  ...groupBy.filter((key) => key !== GaAcquisitionDimensions.date).map((key) => asc22(dimensions[key]))
61395
61586
  ];
61396
61587
  const rows = grouped.orderBy(...order).limit(limit).offset(offset).all();
61397
- const coverage = app.db.select({ earliestDate: sql38`min(${gaAcquisitionDaily.date})`, latestDate: sql38`max(${gaAcquisitionDaily.date})` }).from(gaAcquisitionDaily).where(eq86(gaAcquisitionDaily.projectId, project.id)).get();
61398
- const channelGroups = app.db.selectDistinct({ channel: gaAcquisitionDaily.channelGroup }).from(gaAcquisitionDaily).where(and74(...windowFilters)).orderBy(asc22(gaAcquisitionDaily.channelGroup)).all().map((row) => row.channel);
61399
- const state = app.db.select().from(gaMeasurementSyncStates).where(eq86(gaMeasurementSyncStates.projectId, project.id)).get();
61588
+ const coverage = app.db.select({ earliestDate: sql38`min(${gaAcquisitionDaily.date})`, latestDate: sql38`max(${gaAcquisitionDaily.date})` }).from(gaAcquisitionDaily).where(eq87(gaAcquisitionDaily.projectId, project.id)).get();
61589
+ const channelGroups = app.db.selectDistinct({ channel: gaAcquisitionDaily.channelGroup }).from(gaAcquisitionDaily).where(and75(...windowFilters)).orderBy(asc22(gaAcquisitionDaily.channelGroup)).all().map((row) => row.channel);
61590
+ const state = app.db.select().from(gaMeasurementSyncStates).where(eq87(gaMeasurementSyncStates.projectId, project.id)).get();
61400
61591
  const response = {
61401
61592
  source: "ga4-acquisition",
61402
61593
  status: state?.acquisitionStatus ?? "never-synced",
@@ -63128,7 +63319,7 @@ async function wordpressRoutes(app, opts) {
63128
63319
 
63129
63320
  // ../api-routes/src/backlinks.ts
63130
63321
  import crypto53 from "crypto";
63131
- import { and as and77, asc as asc23, desc as desc40, eq as eq88, sql as sql39 } from "drizzle-orm";
63322
+ import { and as and78, asc as asc23, desc as desc40, eq as eq89, sql as sql39 } from "drizzle-orm";
63132
63323
 
63133
63324
  // ../integration-commoncrawl/src/constants.ts
63134
63325
  import os2 from "os";
@@ -63578,7 +63769,7 @@ function pruneCachedRelease(release, opts = {}) {
63578
63769
  }
63579
63770
 
63580
63771
  // ../api-routes/src/backlinks-filter.ts
63581
- import { and as and75, ne as ne15, notLike } from "drizzle-orm";
63772
+ import { and as and76, ne as ne15, notLike } from "drizzle-orm";
63582
63773
  var BACKLINK_FILTER_PATTERNS = [
63583
63774
  "*.google.com",
63584
63775
  "*.googleusercontent.com",
@@ -63601,23 +63792,23 @@ function backlinkCrawlerExclusionClause() {
63601
63792
  conditions.push(ne15(backlinkDomains.linkingDomain, pattern));
63602
63793
  }
63603
63794
  }
63604
- const combined = and75(...conditions);
63795
+ const combined = and76(...conditions);
63605
63796
  if (!combined) throw new Error("BACKLINK_FILTER_PATTERNS is unexpectedly empty");
63606
63797
  return combined;
63607
63798
  }
63608
63799
 
63609
63800
  // ../api-routes/src/backlinks-source-state.ts
63610
- import { and as and76, eq as eq87, isNotNull as isNotNull8, or as or17 } from "drizzle-orm";
63801
+ import { and as and77, eq as eq88, isNotNull as isNotNull8, or as or17 } from "drizzle-orm";
63611
63802
  function readCommonCrawlSourceState(db, projectId) {
63612
63803
  const completedReleases = db.select({ status: ccReleaseSyncs.status }).from(ccReleaseSyncs).where(or17(
63613
- eq87(ccReleaseSyncs.status, CcReleaseSyncStatuses.ready),
63804
+ eq88(ccReleaseSyncs.status, CcReleaseSyncStatuses.ready),
63614
63805
  isNotNull8(ccReleaseSyncs.queryFinishedAt)
63615
63806
  )).all();
63616
63807
  const hasReadyRelease = completedReleases.length > 0;
63617
63808
  const hasCurrentReadyRelease = completedReleases.some((release) => release.status === CcReleaseSyncStatuses.ready);
63618
- const hasProjectData = db.select({ id: backlinkSummaries.id }).from(backlinkSummaries).where(and76(
63619
- eq87(backlinkSummaries.projectId, projectId),
63620
- eq87(backlinkSummaries.source, BacklinkSources.commoncrawl)
63809
+ const hasProjectData = db.select({ id: backlinkSummaries.id }).from(backlinkSummaries).where(and77(
63810
+ eq88(backlinkSummaries.projectId, projectId),
63811
+ eq88(backlinkSummaries.source, BacklinkSources.commoncrawl)
63621
63812
  )).limit(1).get() !== void 0;
63622
63813
  return { hasReadyRelease, hasCurrentReadyRelease, hasProjectData, connected: hasReadyRelease && hasProjectData };
63623
63814
  }
@@ -63688,11 +63879,11 @@ function mapRunRow(row) {
63688
63879
  }
63689
63880
  function latestSummaryForProject(db, projectId, source, release) {
63690
63881
  const conditions = [
63691
- eq88(backlinkSummaries.projectId, projectId),
63692
- eq88(backlinkSummaries.source, source)
63882
+ eq89(backlinkSummaries.projectId, projectId),
63883
+ eq89(backlinkSummaries.source, source)
63693
63884
  ];
63694
- if (release) conditions.push(eq88(backlinkSummaries.release, release));
63695
- const rows = db.select().from(backlinkSummaries).where(and77(...conditions)).orderBy(desc40(backlinkSummaries.queriedAt)).all();
63885
+ if (release) conditions.push(eq89(backlinkSummaries.release, release));
63886
+ const rows = db.select().from(backlinkSummaries).where(and78(...conditions)).orderBy(desc40(backlinkSummaries.queriedAt)).all();
63696
63887
  return rows.reduce((latest, row) => {
63697
63888
  if (!latest) return row;
63698
63889
  const newer = isValidReleaseId(row.release) && isValidReleaseId(latest.release) && compareReleaseIds(row.release, latest.release) > 0;
@@ -63705,12 +63896,12 @@ function parseExcludeCrawlers(value) {
63705
63896
  return lower === "1" || lower === "true" || lower === "yes";
63706
63897
  }
63707
63898
  function computeFilteredSummary(db, base) {
63708
- const baseDomainCondition = and77(
63709
- eq88(backlinkDomains.projectId, base.projectId),
63710
- eq88(backlinkDomains.source, base.source),
63711
- eq88(backlinkDomains.release, base.release)
63899
+ const baseDomainCondition = and78(
63900
+ eq89(backlinkDomains.projectId, base.projectId),
63901
+ eq89(backlinkDomains.source, base.source),
63902
+ eq89(backlinkDomains.release, base.release)
63712
63903
  );
63713
- const filteredCondition = and77(baseDomainCondition, backlinkCrawlerExclusionClause());
63904
+ const filteredCondition = and78(baseDomainCondition, backlinkCrawlerExclusionClause());
63714
63905
  const unfilteredAgg = db.select({
63715
63906
  count: sql39`count(*)`,
63716
63907
  total: sql39`coalesce(sum(${backlinkDomains.numHosts}), 0)`
@@ -63751,7 +63942,7 @@ function computeFilteredReleaseTotals(db, projectId, source) {
63751
63942
  SELECT ${backlinkDomains.release} AS release, ${backlinkDomains.numHosts} AS num_hosts, ${kept} AS kept,
63752
63943
  ROW_NUMBER() OVER (PARTITION BY ${backlinkDomains.release}, ${kept} ORDER BY ${backlinkDomains.numHosts} DESC) AS kept_rank
63753
63944
  FROM ${backlinkDomains}
63754
- WHERE ${and77(eq88(backlinkDomains.projectId, projectId), eq88(backlinkDomains.source, source))}
63945
+ WHERE ${and78(eq89(backlinkDomains.projectId, projectId), eq89(backlinkDomains.source, source))}
63755
63946
  )
63756
63947
  GROUP BY release`);
63757
63948
  return new Map(rows.map((row) => {
@@ -63765,13 +63956,13 @@ function computeFilteredReleaseTotals(db, projectId, source) {
63765
63956
  }));
63766
63957
  }
63767
63958
  function buildSourceAvailability(db, projectId, source, connected2, excludeCrawlers) {
63768
- const summary3 = db.select().from(backlinkSummaries).where(and77(eq88(backlinkSummaries.projectId, projectId), eq88(backlinkSummaries.source, source))).orderBy(desc40(backlinkSummaries.queriedAt)).limit(1).get();
63959
+ const summary3 = db.select().from(backlinkSummaries).where(and78(eq89(backlinkSummaries.projectId, projectId), eq89(backlinkSummaries.source, source))).orderBy(desc40(backlinkSummaries.queriedAt)).limit(1).get();
63769
63960
  let totalLinkingDomains = summary3?.totalLinkingDomains ?? 0;
63770
63961
  if (summary3 && excludeCrawlers) {
63771
- const filtered = db.select({ count: sql39`count(*)` }).from(backlinkDomains).where(and77(
63772
- eq88(backlinkDomains.projectId, projectId),
63773
- eq88(backlinkDomains.source, source),
63774
- eq88(backlinkDomains.release, summary3.release),
63962
+ const filtered = db.select({ count: sql39`count(*)` }).from(backlinkDomains).where(and78(
63963
+ eq89(backlinkDomains.projectId, projectId),
63964
+ eq89(backlinkDomains.source, source),
63965
+ eq89(backlinkDomains.release, summary3.release),
63775
63966
  backlinkCrawlerExclusionClause()
63776
63967
  )).get();
63777
63968
  totalLinkingDomains = Number(filtered?.count ?? 0);
@@ -63841,7 +64032,7 @@ async function backlinksRoutes(app, opts) {
63841
64032
  "@duckdb/node-api is not installed. Run `canonry backlinks install` to enable the backlinks feature."
63842
64033
  );
63843
64034
  }
63844
- const existing = app.db.select().from(ccReleaseSyncs).where(eq88(ccReleaseSyncs.release, release)).get();
64035
+ const existing = app.db.select().from(ccReleaseSyncs).where(eq89(ccReleaseSyncs.release, release)).get();
63845
64036
  const now = (/* @__PURE__ */ new Date()).toISOString();
63846
64037
  if (existing) {
63847
64038
  if (NON_TERMINAL_SYNC_STATUSES.has(existing.status)) {
@@ -63852,9 +64043,9 @@ async function backlinksRoutes(app, opts) {
63852
64043
  phaseDetail: null,
63853
64044
  error: null,
63854
64045
  updatedAt: now
63855
- }).where(eq88(ccReleaseSyncs.id, existing.id)).run();
64046
+ }).where(eq89(ccReleaseSyncs.id, existing.id)).run();
63856
64047
  opts.onReleaseSyncRequested(existing.id, release);
63857
- const refreshed = app.db.select().from(ccReleaseSyncs).where(eq88(ccReleaseSyncs.id, existing.id)).get();
64048
+ const refreshed = app.db.select().from(ccReleaseSyncs).where(eq89(ccReleaseSyncs.id, existing.id)).get();
63858
64049
  return reply.status(200).send(mapSyncRow(refreshed));
63859
64050
  }
63860
64051
  const id = crypto53.randomUUID();
@@ -63866,7 +64057,7 @@ async function backlinksRoutes(app, opts) {
63866
64057
  updatedAt: now
63867
64058
  }).run();
63868
64059
  opts.onReleaseSyncRequested(id, release);
63869
- const inserted = app.db.select().from(ccReleaseSyncs).where(eq88(ccReleaseSyncs.id, id)).get();
64060
+ const inserted = app.db.select().from(ccReleaseSyncs).where(eq89(ccReleaseSyncs.id, id)).get();
63870
64061
  return reply.status(201).send(mapSyncRow(inserted));
63871
64062
  });
63872
64063
  app.get("/backlinks/syncs/latest", async (_request, reply) => {
@@ -63924,7 +64115,7 @@ async function backlinksRoutes(app, opts) {
63924
64115
  createdAt: now
63925
64116
  }).run();
63926
64117
  opts.onBacklinkExtractRequested(runId, project.id, release);
63927
- const run = app.db.select().from(runs).where(eq88(runs.id, runId)).get();
64118
+ const run = app.db.select().from(runs).where(eq89(runs.id, runId)).get();
63928
64119
  return reply.status(201).send(mapRunRow(run));
63929
64120
  });
63930
64121
  app.get(
@@ -63950,12 +64141,12 @@ async function backlinksRoutes(app, opts) {
63950
64141
  const limit = Math.min(Math.max(parseInt(request.query.limit ?? "50", 10) || 50, 1), 500);
63951
64142
  const offset = Math.max(parseInt(request.query.offset ?? "0", 10) || 0, 0);
63952
64143
  const excludeCrawlers = parseExcludeCrawlers(request.query.excludeCrawlers);
63953
- const baseDomainCondition = and77(
63954
- eq88(backlinkDomains.projectId, project.id),
63955
- eq88(backlinkDomains.source, source),
63956
- eq88(backlinkDomains.release, targetRelease)
64144
+ const baseDomainCondition = and78(
64145
+ eq89(backlinkDomains.projectId, project.id),
64146
+ eq89(backlinkDomains.source, source),
64147
+ eq89(backlinkDomains.release, targetRelease)
63957
64148
  );
63958
- const domainCondition = excludeCrawlers ? and77(baseDomainCondition, backlinkCrawlerExclusionClause()) : baseDomainCondition;
64149
+ const domainCondition = excludeCrawlers ? and78(baseDomainCondition, backlinkCrawlerExclusionClause()) : baseDomainCondition;
63959
64150
  const totalRow = app.db.select({ count: sql39`count(*)` }).from(backlinkDomains).where(domainCondition).get();
63960
64151
  const rows = app.db.select({
63961
64152
  linkingDomain: backlinkDomains.linkingDomain,
@@ -63979,7 +64170,7 @@ async function backlinksRoutes(app, opts) {
63979
64170
  async (request, reply) => {
63980
64171
  const project = resolveProject(app.db, request.params.name);
63981
64172
  const source = parseSourceParam(request.query.source);
63982
- const rows = app.db.select().from(backlinkSummaries).where(and77(eq88(backlinkSummaries.projectId, project.id), eq88(backlinkSummaries.source, source))).orderBy(asc23(backlinkSummaries.queriedAt)).all();
64173
+ const rows = app.db.select().from(backlinkSummaries).where(and78(eq89(backlinkSummaries.projectId, project.id), eq89(backlinkSummaries.source, source))).orderBy(asc23(backlinkSummaries.queriedAt)).all();
63983
64174
  const response = rows.map((r) => ({
63984
64175
  release: r.release,
63985
64176
  totalLinkingDomains: r.totalLinkingDomains,
@@ -64006,7 +64197,7 @@ async function backlinksRoutes(app, opts) {
64006
64197
  import crypto55 from "crypto";
64007
64198
  import { isIP as isIP3 } from "net";
64008
64199
  import { isDeepStrictEqual } from "util";
64009
- import { and as and79, desc as desc41, eq as eq90, gt as gt7, gte as gte25, inArray as inArray41, lt as lt17, lte as lte26, sql as sql40 } from "drizzle-orm";
64200
+ import { and as and80, desc as desc41, eq as eq91, gt as gt7, gte as gte25, inArray as inArray41, lt as lt18, lte as lte26, sql as sql40 } from "drizzle-orm";
64010
64201
 
64011
64202
  // ../api-routes/src/traffic-limits.ts
64012
64203
  var VERCEL_MAX_SYNC_WINDOW_MS = 24 * 60 * 6e4;
@@ -65349,7 +65540,7 @@ async function ackCloudflareQueueMessages(client, options) {
65349
65540
  }
65350
65541
 
65351
65542
  // ../api-routes/src/traffic-sync-lease.ts
65352
- import { and as and78, eq as eq89, isNull as isNull13, lte as lte25, or as or18 } from "drizzle-orm";
65543
+ import { and as and79, eq as eq90, isNull as isNull13, lte as lte25, or as or18 } from "drizzle-orm";
65353
65544
  function leaseExpiresAt(now, ttlMs) {
65354
65545
  const nowMs = Date.parse(now);
65355
65546
  if (!Number.isFinite(nowMs)) throw new RangeError("Traffic sync lease now must be an ISO instant");
@@ -65362,12 +65553,12 @@ function tryClaimTrafficSyncLease(input) {
65362
65553
  const changed = tx.update(trafficSources).set({
65363
65554
  syncLeaseOwner: input.owner,
65364
65555
  syncLeaseExpiresAt: expiresAt
65365
- }).where(and78(
65366
- eq89(trafficSources.id, input.sourceId),
65556
+ }).where(and79(
65557
+ eq90(trafficSources.id, input.sourceId),
65367
65558
  or18(
65368
65559
  isNull13(trafficSources.syncLeaseOwner),
65369
65560
  lte25(trafficSources.syncLeaseExpiresAt, input.now),
65370
- eq89(trafficSources.syncLeaseOwner, input.owner)
65561
+ eq90(trafficSources.syncLeaseOwner, input.owner)
65371
65562
  )
65372
65563
  )).run();
65373
65564
  return changed.changes === 1;
@@ -65378,9 +65569,9 @@ function releaseTrafficSyncLease(input) {
65378
65569
  const changed = tx.update(trafficSources).set({
65379
65570
  syncLeaseOwner: null,
65380
65571
  syncLeaseExpiresAt: null
65381
- }).where(and78(
65382
- eq89(trafficSources.id, input.sourceId),
65383
- eq89(trafficSources.syncLeaseOwner, input.owner)
65572
+ }).where(and79(
65573
+ eq90(trafficSources.id, input.sourceId),
65574
+ eq90(trafficSources.syncLeaseOwner, input.owner)
65384
65575
  )).run();
65385
65576
  return changed.changes === 1;
65386
65577
  }, { behavior: "immediate" });
@@ -65447,14 +65638,14 @@ async function loadQueueReceiptPolicy(db, sourceId, readRetentionSeconds, warn2)
65447
65638
  if (liveTtlMs === null) return { ttlMsAt: () => CLOUDFLARE_QUEUE_RECEIPT_TTL_MS };
65448
65639
  const ttlMs = liveTtlMs;
65449
65640
  const now = (/* @__PURE__ */ new Date()).toISOString();
65450
- const alive = and79(eq90(trafficEventReceipts.sourceId, sourceId), gt7(trafficEventReceipts.expiresAt, now));
65641
+ const alive = and80(eq91(trafficEventReceipts.sourceId, sourceId), gt7(trafficEventReceipts.expiresAt, now));
65451
65642
  const lifetimeMs = sql40`(julianday(${trafficEventReceipts.expiresAt}) - julianday(${trafficEventReceipts.receivedAt})) * ${DAY_MS5}`;
65452
65643
  const shortest = db.select({ ms: sql40`min(${lifetimeMs})` }).from(trafficEventReceipts).where(alive).get()?.ms ?? null;
65453
65644
  if (shortest !== null && shortest < ttlMs - 1e3) {
65454
65645
  const extended = sql40`strftime('%Y-%m-%dT%H:%M:%fZ', julianday(${trafficEventReceipts.receivedAt}) + ${ttlMs / DAY_MS5})`;
65455
- db.update(trafficEventReceipts).set({ expiresAt: extended }).where(and79(alive, lt17(trafficEventReceipts.expiresAt, extended))).run();
65646
+ db.update(trafficEventReceipts).set({ expiresAt: extended }).where(and80(alive, lt18(trafficEventReceipts.expiresAt, extended))).run();
65456
65647
  }
65457
- const floorExpiry = db.select({ at: sql40`max(${trafficEventReceipts.expiresAt})` }).from(trafficEventReceipts).where(and79(alive, sql40`${lifetimeMs} < ${CLOUDFLARE_QUEUE_RECEIPT_TTL_MS - 1e3}`)).get()?.at ?? null;
65648
+ const floorExpiry = db.select({ at: sql40`max(${trafficEventReceipts.expiresAt})` }).from(trafficEventReceipts).where(and80(alive, sql40`${lifetimeMs} < ${CLOUDFLARE_QUEUE_RECEIPT_TTL_MS - 1e3}`)).get()?.at ?? null;
65458
65649
  const floorMs = floorExpiry ? Date.parse(floorExpiry) : 0;
65459
65650
  return { ttlMsAt: (receivedAt) => Math.max(ttlMs, floorMs - Date.parse(receivedAt)) };
65460
65651
  }
@@ -65509,7 +65700,7 @@ function cloudflareIngestRateLimitKey(request, store, db) {
65509
65700
  const authenticated = authenticateCloudflareIngest(request, store);
65510
65701
  const params = request.params;
65511
65702
  if (authenticated && typeof params.name === "string" && authenticated.credential.projectName === params.name) {
65512
- const source = db.select().from(trafficSources).where(eq90(trafficSources.id, authenticated.credential.sourceId)).get();
65703
+ const source = db.select().from(trafficSources).where(eq91(trafficSources.id, authenticated.credential.sourceId)).get();
65513
65704
  if (source && source.sourceType === TrafficSourceTypes.cloudflare && source.status === TrafficSourceStatuses.connected && parseDirectPushCloudflareSourceConfig(source.configJson) && timingSafeEqualHex(authenticated.bearerHash, source.ingestTokenHash)) {
65514
65705
  return `cloudflare-source:${authenticated.credential.sourceId}`;
65515
65706
  }
@@ -65581,22 +65772,39 @@ function assertCloudflareIngestUrlOutsideWorkerRoute(ingestUrl, workerRouteHost)
65581
65772
  function emptyTrafficSeriesPoint(bucket) {
65582
65773
  return { bucket, crawlerHits: 0, aiUserFetchHits: 0, aiReferralHits: 0, aiReferralLandedHits: 0, crawlerContentHits: 0, measured: true };
65583
65774
  }
65584
- function completeTrafficSeries(windowStart, windowEnd, granularity, pointsByBucket) {
65585
- const cursor = new Date(windowStart);
65586
- const last = new Date(windowEnd);
65587
- if (granularity === TrafficSeriesGranularities.day) {
65588
- cursor.setUTCHours(0, 0, 0, 0);
65589
- last.setUTCHours(0, 0, 0, 0);
65590
- } else {
65591
- cursor.setUTCMinutes(0, 0, 0);
65592
- last.setUTCMinutes(0, 0, 0);
65775
+ var SERIES_BUCKET_LENGTH = {
65776
+ [TrafficSeriesGranularities.hour]: null,
65777
+ [TrafficSeriesGranularities.day]: 10,
65778
+ [TrafficSeriesGranularities.month]: 7
65779
+ };
65780
+ function seriesBucketSql(tsHour, granularity) {
65781
+ const length = SERIES_BUCKET_LENGTH[granularity];
65782
+ return length === null ? tsHour : sql40`substr(${tsHour}, 1, ${sql40.raw(String(length))})`;
65783
+ }
65784
+ function seriesBucketOf(iso2, granularity) {
65785
+ const length = SERIES_BUCKET_LENGTH[granularity];
65786
+ return length === null ? iso2.slice(0, 13) : iso2.slice(0, length);
65787
+ }
65788
+ function seriesBucketStartMs(bucket, granularity) {
65789
+ if (granularity === TrafficSeriesGranularities.month) return Date.parse(`${bucket}-01T00:00:00.000Z`);
65790
+ if (granularity === TrafficSeriesGranularities.day) return Date.parse(`${bucket}T00:00:00.000Z`);
65791
+ return Date.parse(bucket.length === 13 ? `${bucket}:00:00.000Z` : bucket);
65792
+ }
65793
+ function nextSeriesBucketStartMs(startMs, granularity) {
65794
+ if (granularity === TrafficSeriesGranularities.month) {
65795
+ const start = new Date(startMs);
65796
+ return Date.UTC(start.getUTCFullYear(), start.getUTCMonth() + 1, 1);
65593
65797
  }
65798
+ return startMs + (granularity === TrafficSeriesGranularities.day ? 864e5 : 36e5);
65799
+ }
65800
+ function completeTrafficSeries(windowStart, windowEnd, granularity, pointsByBucket) {
65801
+ const firstBucket = seriesBucketOf(windowStart.toISOString(), granularity);
65802
+ const lastBucketStartMs = seriesBucketStartMs(seriesBucketOf(windowEnd.toISOString(), granularity), granularity);
65594
65803
  const points = [];
65595
- while (cursor <= last) {
65596
- const bucket = granularity === TrafficSeriesGranularities.day ? cursor.toISOString().slice(0, 10) : cursor.toISOString();
65804
+ for (let ms = seriesBucketStartMs(firstBucket, granularity); ms <= lastBucketStartMs; ms = nextSeriesBucketStartMs(ms, granularity)) {
65805
+ const iso2 = new Date(ms).toISOString();
65806
+ const bucket = granularity === TrafficSeriesGranularities.hour ? iso2 : seriesBucketOf(iso2, granularity);
65597
65807
  points.push(pointsByBucket.get(bucket) ?? emptyTrafficSeriesPoint(bucket));
65598
- if (granularity === TrafficSeriesGranularities.day) cursor.setUTCDate(cursor.getUTCDate() + 1);
65599
- else cursor.setUTCHours(cursor.getUTCHours() + 1);
65600
65808
  }
65601
65809
  return points;
65602
65810
  }
@@ -65752,7 +65960,7 @@ async function defaultResolveAccessToken(record2) {
65752
65960
  );
65753
65961
  }
65754
65962
  function hasConnectedTrafficSourceSibling(db, projectId, sourceId) {
65755
- return db.select().from(trafficSources).where(eq90(trafficSources.projectId, projectId)).all().some((row) => row.id !== sourceId && row.status === TrafficSourceStatuses.connected);
65963
+ return db.select().from(trafficSources).where(eq91(trafficSources.projectId, projectId)).all().some((row) => row.id !== sourceId && row.status === TrafficSourceStatuses.connected);
65756
65964
  }
65757
65965
  function isAuthoritativeTrafficSource(db, source) {
65758
65966
  return (source.status === TrafficSourceStatuses.connected || source.status === TrafficSourceStatuses.error) && !hasConnectedTrafficSourceSibling(db, source.projectId, source.id);
@@ -65779,13 +65987,13 @@ function wordpressBaseUrlIdentity(value) {
65779
65987
  }
65780
65988
  }
65781
65989
  function bindTrafficSyncSchedule(tx, projectId, sourceId, now, createIfMissing = true) {
65782
- const schedule = tx.select().from(schedules).where(and79(
65783
- eq90(schedules.projectId, projectId),
65784
- eq90(schedules.kind, SchedulableRunKinds["traffic-sync"])
65990
+ const schedule = tx.select().from(schedules).where(and80(
65991
+ eq91(schedules.projectId, projectId),
65992
+ eq91(schedules.kind, SchedulableRunKinds["traffic-sync"])
65785
65993
  )).get();
65786
65994
  if (schedule) {
65787
65995
  if (schedule.sourceId === sourceId) return { changed: false, created: false };
65788
- tx.update(schedules).set({ sourceId, updatedAt: nextScheduleUpdatedAt(schedule.updatedAt, Date.parse(now)) }).where(eq90(schedules.id, schedule.id)).run();
65996
+ tx.update(schedules).set({ sourceId, updatedAt: nextScheduleUpdatedAt(schedule.updatedAt, Date.parse(now)) }).where(eq91(schedules.id, schedule.id)).run();
65789
65997
  return { changed: true, created: false };
65790
65998
  }
65791
65999
  if (!createIfMissing) return { changed: false, created: false };
@@ -65827,7 +66035,7 @@ async function runBackfillTask(options) {
65827
66035
  };
65828
66036
  const renewLease2 = setInterval(() => {
65829
66037
  try {
65830
- const latest = app.db.select().from(trafficSources).where(eq90(trafficSources.id, sourceRow2.id)).get();
66038
+ const latest = app.db.select().from(trafficSources).where(eq91(trafficSources.id, sourceRow2.id)).get();
65831
66039
  if (!latest || !isAuthoritativeTrafficSource(app.db, latest) || !isSameTrafficSourceGeneration(latest, sourceRow2)) {
65832
66040
  clearInterval(renewLease2);
65833
66041
  stopped.abort();
@@ -65875,10 +66083,10 @@ async function runBackfillTaskHoldingLease(options, signal) {
65875
66083
  const failedAt = (/* @__PURE__ */ new Date()).toISOString();
65876
66084
  try {
65877
66085
  app.db.transaction((tx) => {
65878
- tx.update(runs).set({ status: RunStatuses.failed, error: msg, finishedAt: failedAt }).where(eq90(runs.id, runId)).run();
65879
- const latestSource = tx.select().from(trafficSources).where(eq90(trafficSources.id, sourceRow2.id)).get();
66086
+ tx.update(runs).set({ status: RunStatuses.failed, error: msg, finishedAt: failedAt }).where(eq91(runs.id, runId)).run();
66087
+ const latestSource = tx.select().from(trafficSources).where(eq91(trafficSources.id, sourceRow2.id)).get();
65880
66088
  if (!preserveSourceState && latestSource && isAuthoritativeTrafficSource(tx, latestSource) && isSameBackfillSource(latestSource, sourceRow2, leaseOwner)) {
65881
- tx.update(trafficSources).set({ status: TrafficSourceStatuses.error, lastError: msg, updatedAt: failedAt }).where(eq90(trafficSources.id, sourceRow2.id)).run();
66089
+ tx.update(trafficSources).set({ status: TrafficSourceStatuses.error, lastError: msg, updatedAt: failedAt }).where(eq91(trafficSources.id, sourceRow2.id)).run();
65882
66090
  }
65883
66091
  });
65884
66092
  } catch {
@@ -65910,7 +66118,7 @@ async function runBackfillTaskHoldingLease(options, signal) {
65910
66118
  let emptyOutcome;
65911
66119
  try {
65912
66120
  const completed = app.db.transaction((tx) => {
65913
- const latestSource = tx.select().from(trafficSources).where(eq90(trafficSources.id, sourceRow2.id)).get();
66121
+ const latestSource = tx.select().from(trafficSources).where(eq91(trafficSources.id, sourceRow2.id)).get();
65914
66122
  const stillAuthoritative = latestSource && isAuthoritativeTrafficSource(tx, latestSource) && isSameBackfillSource(latestSource, sourceRow2, leaseOwner);
65915
66123
  if (stillAuthoritative) {
65916
66124
  enforceRawEventSampleRetention(tx, sourceRow2.id, finishedAt2);
@@ -65919,7 +66127,7 @@ async function runBackfillTaskHoldingLease(options, signal) {
65919
66127
  status: RunStatuses.failed,
65920
66128
  error: "Traffic source was deactivated or reconfigured during backfill",
65921
66129
  finishedAt: finishedAt2
65922
- }).where(eq90(runs.id, runId)).run();
66130
+ }).where(eq91(runs.id, runId)).run();
65923
66131
  return Boolean(stillAuthoritative);
65924
66132
  });
65925
66133
  emptyOutcome = completed ? { status: OutcomeStatuses.skipped, reasonCode: OutcomeReasonCodes.NO_DATA } : { status: OutcomeStatuses.failed, reasonCode: OutcomeReasonCodes.NOT_CONNECTED };
@@ -65951,13 +66159,13 @@ async function runBackfillTaskHoldingLease(options, signal) {
65951
66159
  const nextLastSyncedAt = Math.max(currentLastSyncedMs, windowEnd.getTime()) === windowEnd.getTime() ? windowEndIso : sourceRow2.lastSyncedAt;
65952
66160
  try {
65953
66161
  const commitOutcome = app.db.transaction((tx) => {
65954
- const latestSource = tx.select().from(trafficSources).where(eq90(trafficSources.id, sourceRow2.id)).get();
66162
+ const latestSource = tx.select().from(trafficSources).where(eq91(trafficSources.id, sourceRow2.id)).get();
65955
66163
  if (latestSource?.sourceType === TrafficSourceTypes.wordpress) {
65956
66164
  tx.update(runs).set({
65957
66165
  status: RunStatuses.failed,
65958
66166
  error: "Generic WordPress replace backfill is unavailable because retained coverage is unproven.",
65959
66167
  finishedAt
65960
- }).where(eq90(runs.id, runId)).run();
66168
+ }).where(eq91(runs.id, runId)).run();
65961
66169
  return "wordpress-backfill-unsupported";
65962
66170
  }
65963
66171
  if (!latestSource || !isAuthoritativeTrafficSource(tx, latestSource) || !isSameBackfillSource(latestSource, sourceRow2, leaseOwner)) {
@@ -65965,7 +66173,7 @@ async function runBackfillTaskHoldingLease(options, signal) {
65965
66173
  status: RunStatuses.failed,
65966
66174
  error: "Traffic source was deactivated or reconfigured during backfill",
65967
66175
  finishedAt
65968
- }).where(eq90(runs.id, runId)).run();
66176
+ }).where(eq91(runs.id, runId)).run();
65969
66177
  return "source-inactive";
65970
66178
  }
65971
66179
  const rawSampleCutoff = enforceRawEventSampleRetention(
@@ -65974,37 +66182,37 @@ async function runBackfillTaskHoldingLease(options, signal) {
65974
66182
  finishedAt
65975
66183
  );
65976
66184
  tx.delete(crawlerEventsHourly).where(
65977
- and79(
65978
- eq90(crawlerEventsHourly.sourceId, sourceRow2.id),
66185
+ and80(
66186
+ eq91(crawlerEventsHourly.sourceId, sourceRow2.id),
65979
66187
  gte25(crawlerEventsHourly.tsHour, windowStartIso),
65980
66188
  lte26(crawlerEventsHourly.tsHour, windowEndIso)
65981
66189
  )
65982
66190
  ).run();
65983
66191
  tx.delete(aiUserFetchEventsHourly).where(
65984
- and79(
65985
- eq90(aiUserFetchEventsHourly.sourceId, sourceRow2.id),
66192
+ and80(
66193
+ eq91(aiUserFetchEventsHourly.sourceId, sourceRow2.id),
65986
66194
  gte25(aiUserFetchEventsHourly.tsHour, windowStartIso),
65987
66195
  lte26(aiUserFetchEventsHourly.tsHour, windowEndIso)
65988
66196
  )
65989
66197
  ).run();
65990
66198
  clearLikelyFakeAgentHits(tx, { sourceId: sourceRow2.id, windowStart: windowStartIso, windowEnd: windowEndIso });
65991
66199
  tx.delete(aiReferralEventsHourly).where(
65992
- and79(
65993
- eq90(aiReferralEventsHourly.sourceId, sourceRow2.id),
66200
+ and80(
66201
+ eq91(aiReferralEventsHourly.sourceId, sourceRow2.id),
65994
66202
  gte25(aiReferralEventsHourly.tsHour, windowStartIso),
65995
66203
  lte26(aiReferralEventsHourly.tsHour, windowEndIso)
65996
66204
  )
65997
66205
  ).run();
65998
66206
  tx.delete(rawEventSamples).where(
65999
- and79(
66000
- eq90(rawEventSamples.sourceId, sourceRow2.id),
66207
+ and80(
66208
+ eq91(rawEventSamples.sourceId, sourceRow2.id),
66001
66209
  gte25(rawEventSamples.ts, windowStartIso),
66002
66210
  lte26(rawEventSamples.ts, windowEndIso)
66003
66211
  )
66004
66212
  ).run();
66005
66213
  tx.delete(unrecognizedAgentEventsHourly).where(
66006
- and79(
66007
- eq90(unrecognizedAgentEventsHourly.sourceId, sourceRow2.id),
66214
+ and80(
66215
+ eq91(unrecognizedAgentEventsHourly.sourceId, sourceRow2.id),
66008
66216
  gte25(unrecognizedAgentEventsHourly.tsHour, windowStartIso),
66009
66217
  lte26(unrecognizedAgentEventsHourly.tsHour, windowEndIso)
66010
66218
  )
@@ -66212,9 +66420,9 @@ async function runBackfillTaskHoldingLease(options, signal) {
66212
66420
  FROM _migrations WHERE version = 207
66213
66421
  `);
66214
66422
  if (ruleChange?.appliedAt) {
66215
- const olderReferral = tx.select({ sourceId: aiReferralEventsHourly.sourceId }).from(aiReferralEventsHourly).where(and79(
66216
- eq90(aiReferralEventsHourly.sourceId, sourceRow2.id),
66217
- lt17(aiReferralEventsHourly.createdAt, ruleChange.appliedAt)
66423
+ const olderReferral = tx.select({ sourceId: aiReferralEventsHourly.sourceId }).from(aiReferralEventsHourly).where(and80(
66424
+ eq91(aiReferralEventsHourly.sourceId, sourceRow2.id),
66425
+ lt18(aiReferralEventsHourly.createdAt, ruleChange.appliedAt)
66218
66426
  )).limit(1).get();
66219
66427
  if (!olderReferral) olderCountUntil = null;
66220
66428
  }
@@ -66227,8 +66435,8 @@ async function runBackfillTaskHoldingLease(options, signal) {
66227
66435
  aiReferralOlderCountUntil: olderCountUntil,
66228
66436
  ...skipRecovered ? { skippedThroughAt: null } : {},
66229
66437
  updatedAt: finishedAt
66230
- }).where(eq90(trafficSources.id, sourceRow2.id)).run();
66231
- tx.update(runs).set({ status: RunStatuses.completed, finishedAt }).where(eq90(runs.id, runId)).run();
66438
+ }).where(eq91(trafficSources.id, sourceRow2.id)).run();
66439
+ tx.update(runs).set({ status: RunStatuses.completed, finishedAt }).where(eq91(runs.id, runId)).run();
66232
66440
  return "committed";
66233
66441
  });
66234
66442
  reportBackfill(commitOutcome === "committed" ? {
@@ -66401,7 +66609,7 @@ async function trafficRoutes(app, opts) {
66401
66609
  };
66402
66610
  const fallbackName = displayName ?? `Cloud Run \xB7 ${gcpProjectId}${serviceName ? ` / ${serviceName}` : ""}`;
66403
66611
  const { sourceRow: sourceRow2, scheduleChanged } = app.db.transaction((tx) => {
66404
- const activeSource = tx.select().from(trafficSources).where(eq90(trafficSources.projectId, project.id)).all().find((row) => row.sourceType === TrafficSourceTypes["cloud-run"] && row.status !== TrafficSourceStatuses.archived);
66612
+ const activeSource = tx.select().from(trafficSources).where(eq91(trafficSources.projectId, project.id)).all().find((row) => row.sourceType === TrafficSourceTypes["cloud-run"] && row.status !== TrafficSourceStatuses.archived);
66405
66613
  const sourceId = activeSource?.id ?? crypto55.randomUUID();
66406
66614
  const sourceStatus = trafficConnectStatus(tx, project.id, activeSource);
66407
66615
  if (activeSource) {
@@ -66411,7 +66619,7 @@ async function trafficRoutes(app, opts) {
66411
66619
  lastError: null,
66412
66620
  configJson: config,
66413
66621
  updatedAt: now
66414
- }).where(eq90(trafficSources.id, sourceId)).run();
66622
+ }).where(eq91(trafficSources.id, sourceId)).run();
66415
66623
  } else {
66416
66624
  tx.insert(trafficSources).values({
66417
66625
  id: sourceId,
@@ -66437,7 +66645,7 @@ async function trafficRoutes(app, opts) {
66437
66645
  entityId: sourceId
66438
66646
  });
66439
66647
  return {
66440
- sourceRow: tx.select().from(trafficSources).where(eq90(trafficSources.id, sourceId)).get(),
66648
+ sourceRow: tx.select().from(trafficSources).where(eq91(trafficSources.id, sourceId)).get(),
66441
66649
  scheduleChanged: scheduleBinding.changed
66442
66650
  };
66443
66651
  }, { behavior: "immediate" });
@@ -66492,7 +66700,7 @@ async function trafficRoutes(app, opts) {
66492
66700
  const config = { baseUrl, username };
66493
66701
  const fallbackName = displayName ?? `WordPress \xB7 ${new URL(baseUrl).host}`;
66494
66702
  const { sourceRow: sourceRow2, scheduleChanged } = app.db.transaction((tx) => {
66495
- const activeSource = tx.select().from(trafficSources).where(eq90(trafficSources.projectId, project.id)).all().find((row) => row.sourceType === TrafficSourceTypes.wordpress && row.status !== TrafficSourceStatuses.archived);
66703
+ const activeSource = tx.select().from(trafficSources).where(eq91(trafficSources.projectId, project.id)).all().find((row) => row.sourceType === TrafficSourceTypes.wordpress && row.status !== TrafficSourceStatuses.archived);
66496
66704
  const endpointChanged = activeSource !== void 0 && wordpressBaseUrlIdentity(activeSource.configJson.baseUrl) !== wordpressBaseUrlIdentity(baseUrl);
66497
66705
  const sourceId = endpointChanged ? crypto55.randomUUID() : activeSource?.id ?? crypto55.randomUUID();
66498
66706
  const sourceStatus = trafficConnectStatus(tx, project.id, activeSource);
@@ -66503,14 +66711,14 @@ async function trafficRoutes(app, opts) {
66503
66711
  lastError: null,
66504
66712
  configJson: config,
66505
66713
  updatedAt: now
66506
- }).where(eq90(trafficSources.id, sourceId)).run();
66714
+ }).where(eq91(trafficSources.id, sourceId)).run();
66507
66715
  } else {
66508
66716
  if (activeSource) {
66509
66717
  tx.update(trafficSources).set({
66510
66718
  status: TrafficSourceStatuses.archived,
66511
66719
  archivedAt: now,
66512
66720
  updatedAt: now
66513
- }).where(eq90(trafficSources.id, activeSource.id)).run();
66721
+ }).where(eq91(trafficSources.id, activeSource.id)).run();
66514
66722
  }
66515
66723
  tx.insert(trafficSources).values({
66516
66724
  id: sourceId,
@@ -66536,7 +66744,7 @@ async function trafficRoutes(app, opts) {
66536
66744
  entityId: sourceId
66537
66745
  });
66538
66746
  return {
66539
- sourceRow: tx.select().from(trafficSources).where(eq90(trafficSources.id, sourceId)).get(),
66747
+ sourceRow: tx.select().from(trafficSources).where(eq91(trafficSources.id, sourceId)).get(),
66540
66748
  scheduleChanged: scheduleBinding.changed
66541
66749
  };
66542
66750
  }, { behavior: "immediate" });
@@ -66593,7 +66801,7 @@ async function trafficRoutes(app, opts) {
66593
66801
  const config = { projectId, teamId, environment };
66594
66802
  const fallbackName = displayName ?? `Vercel \xB7 ${projectId}`;
66595
66803
  const { sourceRow: sourceRow2, scheduleChanged } = app.db.transaction((tx) => {
66596
- const activeSource = tx.select().from(trafficSources).where(eq90(trafficSources.projectId, project.id)).all().find((row2) => row2.sourceType === TrafficSourceTypes.vercel && row2.status !== TrafficSourceStatuses.archived);
66804
+ const activeSource = tx.select().from(trafficSources).where(eq91(trafficSources.projectId, project.id)).all().find((row2) => row2.sourceType === TrafficSourceTypes.vercel && row2.status !== TrafficSourceStatuses.archived);
66597
66805
  const sourceStatus = trafficConnectStatus(tx, project.id, activeSource);
66598
66806
  let row;
66599
66807
  if (activeSource) {
@@ -66603,8 +66811,8 @@ async function trafficRoutes(app, opts) {
66603
66811
  lastError: null,
66604
66812
  configJson: config,
66605
66813
  updatedAt: now
66606
- }).where(eq90(trafficSources.id, activeSource.id)).run();
66607
- row = tx.select().from(trafficSources).where(eq90(trafficSources.id, activeSource.id)).get();
66814
+ }).where(eq91(trafficSources.id, activeSource.id)).run();
66815
+ row = tx.select().from(trafficSources).where(eq91(trafficSources.id, activeSource.id)).get();
66608
66816
  } else {
66609
66817
  const newId = crypto55.randomUUID();
66610
66818
  tx.insert(trafficSources).values({
@@ -66629,7 +66837,7 @@ async function trafficRoutes(app, opts) {
66629
66837
  createdAt: now,
66630
66838
  updatedAt: now
66631
66839
  }).run();
66632
- row = tx.select().from(trafficSources).where(eq90(trafficSources.id, newId)).get();
66840
+ row = tx.select().from(trafficSources).where(eq91(trafficSources.id, newId)).get();
66633
66841
  }
66634
66842
  const scheduleBinding = sourceStatus === TrafficSourceStatuses.connected ? bindTrafficSyncSchedule(tx, project.id, row.id, now) : { changed: false, created: false };
66635
66843
  writeAuditLog(tx, {
@@ -66673,7 +66881,7 @@ async function trafficRoutes(app, opts) {
66673
66881
  const { displayName: displayName2, zoneId: zoneId2, accountId: accountId2, queueId, queueName, retentionSeconds, apiToken } = parsed.data;
66674
66882
  const workerRouteHost2 = resolveCloudflareWorkerRouteHost(project.canonicalDomain);
66675
66883
  const now2 = (/* @__PURE__ */ new Date()).toISOString();
66676
- const cloudflareSources = app.db.select().from(trafficSources).where(eq90(trafficSources.projectId, project.id)).all().filter((row) => row.sourceType === TrafficSourceTypes.cloudflare && row.status !== TrafficSourceStatuses.archived);
66884
+ const cloudflareSources = app.db.select().from(trafficSources).where(eq91(trafficSources.projectId, project.id)).all().filter((row) => row.sourceType === TrafficSourceTypes.cloudflare && row.status !== TrafficSourceStatuses.archived);
66677
66885
  const sameModeSource = cloudflareSources.find((row) => parseQueuePullCloudflareSourceConfig(row.configJson));
66678
66886
  if (sameModeSource) attempt.update({ action: "reauth" });
66679
66887
  const sourceId2 = sameModeSource?.id ?? crypto55.randomUUID();
@@ -66727,7 +66935,7 @@ async function trafficRoutes(app, opts) {
66727
66935
  let scheduleChanged = false;
66728
66936
  try {
66729
66937
  const result = app.db.transaction((tx) => {
66730
- const currentSameModeSource = tx.select().from(trafficSources).where(eq90(trafficSources.projectId, project.id)).all().find((row) => row.sourceType === TrafficSourceTypes.cloudflare && row.status !== TrafficSourceStatuses.archived && parseQueuePullCloudflareSourceConfig(row.configJson) !== null);
66938
+ const currentSameModeSource = tx.select().from(trafficSources).where(eq91(trafficSources.projectId, project.id)).all().find((row) => row.sourceType === TrafficSourceTypes.cloudflare && row.status !== TrafficSourceStatuses.archived && parseQueuePullCloudflareSourceConfig(row.configJson) !== null);
66731
66939
  if ((currentSameModeSource?.id ?? null) !== (sameModeSource?.id ?? null)) {
66732
66940
  throw operationInProgress("Cloudflare Queue source changed during connect; retry");
66733
66941
  }
@@ -66745,7 +66953,7 @@ async function trafficRoutes(app, opts) {
66745
66953
  queueBacklogObservedAt: null
66746
66954
  } : {},
66747
66955
  updatedAt: now2
66748
- }).where(eq90(trafficSources.id, sourceId2)).run();
66956
+ }).where(eq91(trafficSources.id, sourceId2)).run();
66749
66957
  } else {
66750
66958
  tx.insert(trafficSources).values({
66751
66959
  id: sourceId2,
@@ -66765,7 +66973,7 @@ async function trafficRoutes(app, opts) {
66765
66973
  }
66766
66974
  const changed = status === TrafficSourceStatuses.connected ? bindTrafficSyncSchedule(tx, project.id, sourceId2, now2).changed : false;
66767
66975
  return {
66768
- row: tx.select().from(trafficSources).where(eq90(trafficSources.id, sourceId2)).get(),
66976
+ row: tx.select().from(trafficSources).where(eq91(trafficSources.id, sourceId2)).get(),
66769
66977
  scheduleChanged: changed
66770
66978
  };
66771
66979
  }, { behavior: "immediate" });
@@ -66815,7 +67023,7 @@ async function trafficRoutes(app, opts) {
66815
67023
  const { deliveryMode, displayName, zoneId, accountId } = parsed.data;
66816
67024
  const workerRouteHost = resolveCloudflareWorkerRouteHost(project.canonicalDomain);
66817
67025
  const now = (/* @__PURE__ */ new Date()).toISOString();
66818
- const activeSource = app.db.select().from(trafficSources).where(eq90(trafficSources.projectId, project.id)).all().find((row) => row.sourceType === TrafficSourceTypes.cloudflare && row.status !== TrafficSourceStatuses.archived && parseDirectPushCloudflareSourceConfig(row.configJson) !== null);
67026
+ const activeSource = app.db.select().from(trafficSources).where(eq91(trafficSources.projectId, project.id)).all().find((row) => row.sourceType === TrafficSourceTypes.cloudflare && row.status !== TrafficSourceStatuses.archived && parseDirectPushCloudflareSourceConfig(row.configJson) !== null);
66819
67027
  if (activeSource) attempt.update({ action: "reauth" });
66820
67028
  const sourceId = activeSource?.id ?? crypto55.randomUUID();
66821
67029
  const parsedActiveConfig = activeSource ? parseDirectPushCloudflareSourceConfig(activeSource.configJson) : null;
@@ -66883,7 +67091,7 @@ async function trafficRoutes(app, opts) {
66883
67091
  let scheduleRemoved = false;
66884
67092
  try {
66885
67093
  const result = app.db.transaction((tx) => {
66886
- const currentActiveSource = tx.select().from(trafficSources).where(eq90(trafficSources.projectId, project.id)).all().find((row2) => row2.sourceType === TrafficSourceTypes.cloudflare && row2.status !== TrafficSourceStatuses.archived && parseDirectPushCloudflareSourceConfig(row2.configJson) !== null);
67094
+ const currentActiveSource = tx.select().from(trafficSources).where(eq91(trafficSources.projectId, project.id)).all().find((row2) => row2.sourceType === TrafficSourceTypes.cloudflare && row2.status !== TrafficSourceStatuses.archived && parseDirectPushCloudflareSourceConfig(row2.configJson) !== null);
66887
67095
  if ((currentActiveSource?.id ?? null) !== (activeSource?.id ?? null)) {
66888
67096
  throw operationInProgress("Cloudflare direct-push source changed during connect; retry");
66889
67097
  }
@@ -66897,8 +67105,8 @@ async function trafficRoutes(app, opts) {
66897
67105
  configJson: config,
66898
67106
  ingestTokenHash,
66899
67107
  updatedAt: now
66900
- }).where(eq90(trafficSources.id, currentActiveSource.id)).run();
66901
- row = tx.select().from(trafficSources).where(eq90(trafficSources.id, currentActiveSource.id)).get();
67108
+ }).where(eq91(trafficSources.id, currentActiveSource.id)).run();
67109
+ row = tx.select().from(trafficSources).where(eq91(trafficSources.id, currentActiveSource.id)).get();
66902
67110
  } else {
66903
67111
  tx.insert(trafficSources).values({
66904
67112
  id: sourceId,
@@ -66918,16 +67126,16 @@ async function trafficRoutes(app, opts) {
66918
67126
  createdAt: now,
66919
67127
  updatedAt: now
66920
67128
  }).run();
66921
- row = tx.select().from(trafficSources).where(eq90(trafficSources.id, sourceId)).get();
67129
+ row = tx.select().from(trafficSources).where(eq91(trafficSources.id, sourceId)).get();
66922
67130
  }
66923
67131
  let removed = false;
66924
67132
  if (row.status === TrafficSourceStatuses.connected) {
66925
- const schedule = tx.select().from(schedules).where(and79(
66926
- eq90(schedules.projectId, project.id),
66927
- eq90(schedules.kind, SchedulableRunKinds["traffic-sync"])
67133
+ const schedule = tx.select().from(schedules).where(and80(
67134
+ eq91(schedules.projectId, project.id),
67135
+ eq91(schedules.kind, SchedulableRunKinds["traffic-sync"])
66928
67136
  )).get();
66929
67137
  if (schedule) {
66930
- tx.delete(schedules).where(eq90(schedules.id, schedule.id)).run();
67138
+ tx.delete(schedules).where(eq91(schedules.id, schedule.id)).run();
66931
67139
  removed = true;
66932
67140
  }
66933
67141
  }
@@ -66983,47 +67191,47 @@ async function trafficRoutes(app, opts) {
66983
67191
  };
66984
67192
  }));
66985
67193
  app.post("/projects/:name/traffic/sources/:id/activate", connectionRoute(app, (request) => {
66986
- const source = app.db.select({ sourceType: trafficSources.sourceType }).from(trafficSources).where(eq90(trafficSources.id, request.params.id)).get();
67194
+ const source = app.db.select({ sourceType: trafficSources.sourceType }).from(trafficSources).where(eq91(trafficSources.id, request.params.id)).get();
66987
67195
  const integration = source ? TRAFFIC_SOURCE_INTEGRATIONS[source.sourceType] : void 0;
66988
67196
  return integration ? { integration, action: "select" } : void 0;
66989
67197
  }, async (request) => {
66990
67198
  const project = resolveProject(app.db, request.params.name);
66991
67199
  const now = (/* @__PURE__ */ new Date()).toISOString();
66992
67200
  const result = app.db.transaction((tx) => {
66993
- const target = tx.select().from(trafficSources).where(eq90(trafficSources.id, request.params.id)).get();
67201
+ const target = tx.select().from(trafficSources).where(eq91(trafficSources.id, request.params.id)).get();
66994
67202
  if (!target || target.projectId !== project.id || target.status === TrafficSourceStatuses.archived) {
66995
67203
  throw notFound("Traffic source", request.params.id);
66996
67204
  }
66997
67205
  const { usesPullSchedule } = validateTrafficSourceCredential(target, project.name);
66998
- const siblings = tx.select().from(trafficSources).where(eq90(trafficSources.projectId, project.id)).all();
67206
+ const siblings = tx.select().from(trafficSources).where(eq91(trafficSources.projectId, project.id)).all();
66999
67207
  for (const sibling of siblings) {
67000
67208
  if (sibling.id === target.id || sibling.status === TrafficSourceStatuses.archived) continue;
67001
67209
  tx.update(trafficSources).set({
67002
67210
  status: TrafficSourceStatuses.paused,
67003
67211
  updatedAt: now
67004
- }).where(eq90(trafficSources.id, sibling.id)).run();
67212
+ }).where(eq91(trafficSources.id, sibling.id)).run();
67005
67213
  }
67006
67214
  tx.update(trafficSources).set({
67007
67215
  status: TrafficSourceStatuses.connected,
67008
67216
  lastError: null,
67009
67217
  archivedAt: null,
67010
67218
  updatedAt: now
67011
- }).where(eq90(trafficSources.id, target.id)).run();
67219
+ }).where(eq91(trafficSources.id, target.id)).run();
67012
67220
  let scheduleAction = null;
67013
67221
  if (usesPullSchedule) {
67014
67222
  bindTrafficSyncSchedule(tx, project.id, target.id, now);
67015
67223
  scheduleAction = "upsert";
67016
67224
  } else {
67017
- const schedule = tx.select().from(schedules).where(and79(
67018
- eq90(schedules.projectId, project.id),
67019
- eq90(schedules.kind, SchedulableRunKinds["traffic-sync"])
67225
+ const schedule = tx.select().from(schedules).where(and80(
67226
+ eq91(schedules.projectId, project.id),
67227
+ eq91(schedules.kind, SchedulableRunKinds["traffic-sync"])
67020
67228
  )).get();
67021
67229
  if (schedule) {
67022
- tx.delete(schedules).where(eq90(schedules.id, schedule.id)).run();
67230
+ tx.delete(schedules).where(eq91(schedules.id, schedule.id)).run();
67023
67231
  scheduleAction = "delete";
67024
67232
  }
67025
67233
  }
67026
- const source = tx.select().from(trafficSources).where(eq90(trafficSources.id, target.id)).get();
67234
+ const source = tx.select().from(trafficSources).where(eq91(trafficSources.id, target.id)).get();
67027
67235
  writeAuditLog(tx, {
67028
67236
  projectId: project.id,
67029
67237
  actor: "api",
@@ -67091,7 +67299,7 @@ async function trafficRoutes(app, opts) {
67091
67299
  }
67092
67300
  const { bearerHash, credential } = authenticated;
67093
67301
  const sourceId = credential.sourceId;
67094
- const sourceRow2 = app.db.select().from(trafficSources).where(eq90(trafficSources.id, sourceId)).get();
67302
+ const sourceRow2 = app.db.select().from(trafficSources).where(eq91(trafficSources.id, sourceId)).get();
67095
67303
  if (!sourceRow2 || sourceRow2.sourceType !== TrafficSourceTypes.cloudflare || sourceRow2.status !== TrafficSourceStatuses.connected || !parseDirectPushCloudflareSourceConfig(sourceRow2.configJson) || !timingSafeEqualHex(bearerHash, sourceRow2.ingestTokenHash)) {
67096
67304
  throw authRequired();
67097
67305
  }
@@ -67169,7 +67377,7 @@ async function trafficRoutes(app, opts) {
67169
67377
  });
67170
67378
  app.post("/projects/:name/traffic/sources/:id/sync", async (request) => {
67171
67379
  const project = resolveProject(app.db, request.params.name);
67172
- const initialSourceRow = app.db.select().from(trafficSources).where(eq90(trafficSources.id, request.params.id)).get();
67380
+ const initialSourceRow = app.db.select().from(trafficSources).where(eq91(trafficSources.id, request.params.id)).get();
67173
67381
  if (!initialSourceRow || initialSourceRow.projectId !== project.id) {
67174
67382
  throw notFound("Traffic source", request.params.id);
67175
67383
  }
@@ -67242,7 +67450,7 @@ async function trafficRoutes(app, opts) {
67242
67450
  });
67243
67451
  const finishSync = (status, error, stopReason) => {
67244
67452
  const queueDrain = drainSummary(stopReason);
67245
- app.db.update(runs).set({ status, finishedAt: committedAt, syncSummary: queueDrain, ...error ? { error } : {} }).where(eq90(runs.id, runId)).run();
67453
+ app.db.update(runs).set({ status, finishedAt: committedAt, syncSummary: queueDrain, ...error ? { error } : {} }).where(eq91(runs.id, runId)).run();
67246
67454
  writeAuditLog(app.db, {
67247
67455
  projectId: project.id,
67248
67456
  actor: "api",
@@ -67417,7 +67625,7 @@ async function trafficRoutes(app, opts) {
67417
67625
  now: (/* @__PURE__ */ new Date()).toISOString(),
67418
67626
  ttlMs: CLOUDFLARE_QUEUE_SYNC_LEASE_TTL_MS
67419
67627
  })) throw new Error("Queue sync lease was lost before acknowledgement");
67420
- const latestSource = app.db.select().from(trafficSources).where(eq90(trafficSources.id, sourceRow2.id)).get();
67628
+ const latestSource = app.db.select().from(trafficSources).where(eq91(trafficSources.id, sourceRow2.id)).get();
67421
67629
  if (!latestSource || latestSource.projectId !== project.id || latestSource.status !== TrafficSourceStatuses.connected || latestSource.syncLeaseOwner !== leaseOwner || !isDeepStrictEqual(latestSource.configJson, sourceRow2.configJson)) {
67422
67630
  throw validationError("Cloudflare Queue source was reconfigured before acknowledgement");
67423
67631
  }
@@ -67506,13 +67714,13 @@ async function trafficRoutes(app, opts) {
67506
67714
  error: safeError,
67507
67715
  finishedAt: failedAt,
67508
67716
  syncSummary: drainSummary(CloudflareQueueDrainStopReasons.error)
67509
- }).where(eq90(runs.id, runId)).run();
67510
- const latestSource = tx.select().from(trafficSources).where(eq90(trafficSources.id, sourceRow2.id)).get();
67717
+ }).where(eq91(runs.id, runId)).run();
67718
+ const latestSource = tx.select().from(trafficSources).where(eq91(trafficSources.id, sourceRow2.id)).get();
67511
67719
  if (latestSource && latestSource.status === TrafficSourceStatuses.connected && latestSource.syncLeaseOwner === leaseOwner && isDeepStrictEqual(latestSource.configJson, sourceRow2.configJson)) {
67512
- tx.update(trafficSources).set({ lastError: safeError, updatedAt: failedAt }).where(and79(
67513
- eq90(trafficSources.id, sourceRow2.id),
67514
- eq90(trafficSources.status, TrafficSourceStatuses.connected),
67515
- eq90(trafficSources.syncLeaseOwner, leaseOwner)
67720
+ tx.update(trafficSources).set({ lastError: safeError, updatedAt: failedAt }).where(and80(
67721
+ eq91(trafficSources.id, sourceRow2.id),
67722
+ eq91(trafficSources.status, TrafficSourceStatuses.connected),
67723
+ eq91(trafficSources.syncLeaseOwner, leaseOwner)
67516
67724
  )).run();
67517
67725
  }
67518
67726
  });
@@ -67531,7 +67739,7 @@ async function trafficRoutes(app, opts) {
67531
67739
  `Sync for source type "${sourceRow2.sourceType}" is not implemented yet \u2014 only cloud-run, wordpress, and vercel are supported in v1.`
67532
67740
  );
67533
67741
  }
67534
- const hasConnectedSibling = sourceRow2.status === TrafficSourceStatuses.error && app.db.select().from(trafficSources).where(eq90(trafficSources.projectId, project.id)).all().some((row) => row.id !== sourceRow2.id && row.status === TrafficSourceStatuses.connected);
67742
+ const hasConnectedSibling = sourceRow2.status === TrafficSourceStatuses.error && app.db.select().from(trafficSources).where(eq91(trafficSources.projectId, project.id)).all().some((row) => row.id !== sourceRow2.id && row.status === TrafficSourceStatuses.connected);
67535
67743
  if (sourceRow2.status === TrafficSourceStatuses.paused || sourceRow2.status === TrafficSourceStatuses.archived || hasConnectedSibling) {
67536
67744
  throw validationError("Traffic source must be active before it can sync");
67537
67745
  }
@@ -67568,10 +67776,10 @@ async function trafficRoutes(app, opts) {
67568
67776
  const markFailed = (msg, errorCode2) => {
67569
67777
  const failedAt = (/* @__PURE__ */ new Date()).toISOString();
67570
67778
  app.db.transaction((tx) => {
67571
- tx.update(runs).set({ status: RunStatuses.failed, error: msg, finishedAt: failedAt }).where(eq90(runs.id, runId)).run();
67572
- const latestSource = tx.select().from(trafficSources).where(eq90(trafficSources.id, sourceRow2.id)).get();
67779
+ tx.update(runs).set({ status: RunStatuses.failed, error: msg, finishedAt: failedAt }).where(eq91(runs.id, runId)).run();
67780
+ const latestSource = tx.select().from(trafficSources).where(eq91(trafficSources.id, sourceRow2.id)).get();
67573
67781
  if (latestSource?.status === TrafficSourceStatuses.connected && isSameTrafficSourceGeneration(latestSource, sourceRow2) && latestSource.syncLeaseOwner === syncLeaseOwner) {
67574
- tx.update(trafficSources).set({ status: TrafficSourceStatuses.error, lastError: msg, updatedAt: failedAt }).where(eq90(trafficSources.id, sourceRow2.id)).run();
67782
+ tx.update(trafficSources).set({ status: TrafficSourceStatuses.error, lastError: msg, updatedAt: failedAt }).where(eq91(trafficSources.id, sourceRow2.id)).run();
67575
67783
  }
67576
67784
  });
67577
67785
  try {
@@ -67665,7 +67873,7 @@ async function trafficRoutes(app, opts) {
67665
67873
  }
67666
67874
  const credential = credentialStore.getConnection(project.name);
67667
67875
  if (!credential) {
67668
- app.db.delete(runs).where(eq90(runs.id, runId)).run();
67876
+ app.db.delete(runs).where(eq91(runs.id, runId)).run();
67669
67877
  throw validationError(
67670
67878
  `No WordPress credential found for project "${project.name}". Run "canonry traffic connect wordpress" first.`
67671
67879
  );
@@ -67699,14 +67907,14 @@ async function trafficRoutes(app, opts) {
67699
67907
  const windowEndIso2 = windowEnd.toISOString();
67700
67908
  const reservedAt = (/* @__PURE__ */ new Date()).toISOString();
67701
67909
  const reservedSource = app.db.transaction((tx) => {
67702
- const latestSource = tx.select().from(trafficSources).where(eq90(trafficSources.id, sourceRow2.id)).get();
67910
+ const latestSource = tx.select().from(trafficSources).where(eq91(trafficSources.id, sourceRow2.id)).get();
67703
67911
  if (!latestSource || !isAuthoritativeTrafficSource(tx, latestSource) || !isSameTrafficSourceGeneration(latestSource, sourceRow2) || latestSource.syncLeaseOwner !== syncLeaseOwner || latestSource.lastCursor || latestSource.wordpressPendingUntil) return void 0;
67704
67912
  tx.update(trafficSources).set({
67705
67913
  lastSyncedAt: windowStartIso2,
67706
67914
  wordpressPendingUntil: windowEndIso2,
67707
67915
  updatedAt: reservedAt
67708
- }).where(eq90(trafficSources.id, sourceRow2.id)).run();
67709
- return tx.select().from(trafficSources).where(eq90(trafficSources.id, sourceRow2.id)).get();
67916
+ }).where(eq91(trafficSources.id, sourceRow2.id)).run();
67917
+ return tx.select().from(trafficSources).where(eq91(trafficSources.id, sourceRow2.id)).get();
67710
67918
  }, { behavior: "immediate" });
67711
67919
  if (!reservedSource) {
67712
67920
  const message = "Traffic source changed while reserving the WordPress sync window; retry the sync.";
@@ -67766,12 +67974,12 @@ async function trafficRoutes(app, opts) {
67766
67974
  auditAction = "traffic.vercel.synced";
67767
67975
  const credentialStore = opts.vercelTrafficCredentialStore;
67768
67976
  if (!credentialStore) {
67769
- app.db.delete(runs).where(eq90(runs.id, runId)).run();
67977
+ app.db.delete(runs).where(eq91(runs.id, runId)).run();
67770
67978
  throw validationError("Vercel traffic credential storage is not configured for this deployment");
67771
67979
  }
67772
67980
  const credential = credentialStore.getConnection(project.name);
67773
67981
  if (!credential) {
67774
- app.db.delete(runs).where(eq90(runs.id, runId)).run();
67982
+ app.db.delete(runs).where(eq91(runs.id, runId)).run();
67775
67983
  throw validationError(
67776
67984
  `No Vercel credential found for project "${project.name}". Run "canonry traffic connect vercel" first.`
67777
67985
  );
@@ -67789,7 +67997,7 @@ async function trafficRoutes(app, opts) {
67789
67997
  if (cappedStartMs > clampedStartMs) {
67790
67998
  const previousSkip = sourceRow2.skippedThroughAt ? Date.parse(sourceRow2.skippedThroughAt) : Number.NaN;
67791
67999
  const skippedThrough = Number.isFinite(previousSkip) ? new Date(Math.max(previousSkip, cappedStartMs)) : new Date(cappedStartMs);
67792
- app.db.update(trafficSources).set({ skippedThroughAt: skippedThrough.toISOString() }).where(eq90(trafficSources.id, sourceRow2.id)).run();
68000
+ app.db.update(trafficSources).set({ skippedThroughAt: skippedThrough.toISOString() }).where(eq91(trafficSources.id, sourceRow2.id)).run();
67793
68001
  request.log.warn(
67794
68002
  {
67795
68003
  sourceId: sourceRow2.id,
@@ -67883,15 +68091,15 @@ async function trafficRoutes(app, opts) {
67883
68091
  throw providerError(`WordPress sync failed: ${msg}`);
67884
68092
  }
67885
68093
  const commitOutcome = app.db.transaction((tx) => {
67886
- const latestRow = tx.select().from(trafficSources).where(eq90(trafficSources.id, sourceRow2.id)).get();
67887
- const latestHasConnectedSibling = tx.select().from(trafficSources).where(eq90(trafficSources.projectId, project.id)).all().some((row) => row.id !== sourceRow2.id && row.status === TrafficSourceStatuses.connected);
68094
+ const latestRow = tx.select().from(trafficSources).where(eq91(trafficSources.id, sourceRow2.id)).get();
68095
+ const latestHasConnectedSibling = tx.select().from(trafficSources).where(eq91(trafficSources.projectId, project.id)).all().some((row) => row.id !== sourceRow2.id && row.status === TrafficSourceStatuses.connected);
67888
68096
  if (!latestRow || latestRow.status !== TrafficSourceStatuses.connected && latestRow.status !== TrafficSourceStatuses.error || latestHasConnectedSibling || latestRow.syncLeaseOwner !== syncLeaseOwner || !isSameTrafficSourceGeneration(latestRow, sourceRow2)) {
67889
68097
  const abortedAt = (/* @__PURE__ */ new Date()).toISOString();
67890
68098
  tx.update(runs).set({
67891
68099
  status: RunStatuses.failed,
67892
68100
  error: "Traffic source was deactivated or reconfigured during sync",
67893
68101
  finishedAt: abortedAt
67894
- }).where(eq90(runs.id, runId)).run();
68102
+ }).where(eq91(runs.id, runId)).run();
67895
68103
  return "source-inactive";
67896
68104
  }
67897
68105
  const previousIds = latestRow.lastEventIds ?? [];
@@ -68177,8 +68385,8 @@ async function trafficRoutes(app, opts) {
68177
68385
  sourceUpdate.lastCursor = nextCursor ?? null;
68178
68386
  sourceUpdate.wordpressPendingUntil = nextCursor ? windowEnd.toISOString() : null;
68179
68387
  }
68180
- tx.update(trafficSources).set(sourceUpdate).where(eq90(trafficSources.id, sourceRow2.id)).run();
68181
- tx.update(runs).set({ status: RunStatuses.completed, finishedAt }).where(eq90(runs.id, runId)).run();
68388
+ tx.update(trafficSources).set(sourceUpdate).where(eq91(trafficSources.id, sourceRow2.id)).run();
68389
+ tx.update(runs).set({ status: RunStatuses.completed, finishedAt }).where(eq91(runs.id, runId)).run();
68182
68390
  return "committed";
68183
68391
  }, { behavior: "immediate" });
68184
68392
  if (commitOutcome === "source-inactive") {
@@ -68247,7 +68455,7 @@ async function trafficRoutes(app, opts) {
68247
68455
  });
68248
68456
  app.post("/projects/:name/traffic/sources/:id/backfill", async (request) => withFeatureOutcome(app, { feature: "server_traffic", operation: "backfill" }, async (recordOutcome) => {
68249
68457
  const project = resolveProject(app.db, request.params.name);
68250
- const sourceRow2 = app.db.select().from(trafficSources).where(eq90(trafficSources.id, request.params.id)).get();
68458
+ const sourceRow2 = app.db.select().from(trafficSources).where(eq91(trafficSources.id, request.params.id)).get();
68251
68459
  if (!sourceRow2 || sourceRow2.projectId !== project.id) {
68252
68460
  throw notFound("Traffic source", request.params.id);
68253
68461
  }
@@ -68372,7 +68580,7 @@ async function trafficRoutes(app, opts) {
68372
68580
  }
68373
68581
  try {
68374
68582
  app.db.transaction((tx) => {
68375
- const latestSource = tx.select().from(trafficSources).where(eq90(trafficSources.id, sourceRow2.id)).get();
68583
+ const latestSource = tx.select().from(trafficSources).where(eq91(trafficSources.id, sourceRow2.id)).get();
68376
68584
  if (!latestSource || !isAuthoritativeTrafficSource(tx, latestSource) || !isSameTrafficSourceGeneration(latestSource, sourceRow2)) {
68377
68585
  throw validationError("Traffic source must remain active and unchanged before it can backfill");
68378
68586
  }
@@ -68422,8 +68630,8 @@ async function trafficRoutes(app, opts) {
68422
68630
  pathNormalized: crawlerEventsHourly.pathNormalized,
68423
68631
  hits: sql40`COALESCE(SUM(${crawlerEventsHourly.hits}), 0)`
68424
68632
  }).from(crawlerEventsHourly).where(
68425
- and79(
68426
- eq90(crawlerEventsHourly.sourceId, row.id),
68633
+ and80(
68634
+ eq91(crawlerEventsHourly.sourceId, row.id),
68427
68635
  gte25(crawlerEventsHourly.tsHour, since)
68428
68636
  )
68429
68637
  ).groupBy(crawlerEventsHourly.pathNormalized).all();
@@ -68432,14 +68640,14 @@ async function trafficRoutes(app, opts) {
68432
68640
  );
68433
68641
  const crawlerTotal = crawlerSegments.content + crawlerSegments.sitemap + crawlerSegments.robots + crawlerSegments.asset + crawlerSegments.other;
68434
68642
  const aiUserFetchTotals = app.db.select({ total: sql40`COALESCE(SUM(${aiUserFetchEventsHourly.hits}), 0)` }).from(aiUserFetchEventsHourly).where(
68435
- and79(
68436
- eq90(aiUserFetchEventsHourly.sourceId, row.id),
68643
+ and80(
68644
+ eq91(aiUserFetchEventsHourly.sourceId, row.id),
68437
68645
  gte25(aiUserFetchEventsHourly.tsHour, since)
68438
68646
  )
68439
68647
  ).get();
68440
68648
  const likelyFakeTotals = app.db.select({ total: sql40`COALESCE(SUM(${aiUserFetchLikelyFakeHourly.hits}), 0)` }).from(aiUserFetchLikelyFakeHourly).where(
68441
- and79(
68442
- eq90(aiUserFetchLikelyFakeHourly.sourceId, row.id),
68649
+ and80(
68650
+ eq91(aiUserFetchLikelyFakeHourly.sourceId, row.id),
68443
68651
  gte25(aiUserFetchLikelyFakeHourly.tsHour, since)
68444
68652
  )
68445
68653
  ).get();
@@ -68448,22 +68656,22 @@ async function trafficRoutes(app, opts) {
68448
68656
  nonhop: sql40`COALESCE(SUM(CASE WHEN ${referralLandedCondition()} THEN ${aiReferralEventsHourly.sessionsOrHits} ELSE 0 END), 0)`,
68449
68657
  landed: sql40`COALESCE(SUM(CASE WHEN ${countableReferralCondition()} THEN ${aiReferralEventsHourly.sessionsOrHits} ELSE 0 END), 0)`
68450
68658
  }).from(aiReferralEventsHourly).where(
68451
- and79(
68452
- eq90(aiReferralEventsHourly.sourceId, row.id),
68659
+ and80(
68660
+ eq91(aiReferralEventsHourly.sourceId, row.id),
68453
68661
  gte25(aiReferralEventsHourly.tsHour, since)
68454
68662
  )
68455
68663
  ).get();
68456
68664
  const sampleTotals = app.db.select({ total: sql40`COUNT(*)` }).from(rawEventSamples).where(
68457
- and79(
68458
- eq90(rawEventSamples.sourceId, row.id),
68665
+ and80(
68666
+ eq91(rawEventSamples.sourceId, row.id),
68459
68667
  gte25(rawEventSamples.ts, since)
68460
68668
  )
68461
68669
  ).get();
68462
68670
  const latestRun = app.db.select().from(runs).where(
68463
- and79(
68464
- eq90(runs.projectId, projectId),
68465
- eq90(runs.kind, RunKinds["traffic-sync"]),
68466
- eq90(runs.sourceId, row.id)
68671
+ and80(
68672
+ eq91(runs.projectId, projectId),
68673
+ eq91(runs.kind, RunKinds["traffic-sync"]),
68674
+ eq91(runs.sourceId, row.id)
68467
68675
  )
68468
68676
  ).orderBy(desc41(runs.startedAt)).limit(1).get();
68469
68677
  return {
@@ -68501,7 +68709,7 @@ async function trafficRoutes(app, opts) {
68501
68709
  const now = (/* @__PURE__ */ new Date()).toISOString();
68502
68710
  let updatedRow;
68503
68711
  app.db.transaction((tx) => {
68504
- const sourceRow2 = tx.select().from(trafficSources).where(and79(eq90(trafficSources.projectId, project.id), eq90(trafficSources.id, request.params.id))).get();
68712
+ const sourceRow2 = tx.select().from(trafficSources).where(and80(eq91(trafficSources.projectId, project.id), eq91(trafficSources.id, request.params.id))).get();
68505
68713
  if (!sourceRow2) {
68506
68714
  throw notFound("traffic source", request.params.id);
68507
68715
  }
@@ -68515,7 +68723,7 @@ async function trafficRoutes(app, opts) {
68515
68723
  `Traffic source "${sourceRow2.id}" is staged. Activate it explicitly instead of resetting it.`
68516
68724
  );
68517
68725
  }
68518
- const hasConnectedSibling = tx.select().from(trafficSources).where(eq90(trafficSources.projectId, project.id)).all().some((row) => row.id !== sourceRow2.id && row.status === TrafficSourceStatuses.connected);
68726
+ const hasConnectedSibling = tx.select().from(trafficSources).where(eq91(trafficSources.projectId, project.id)).all().some((row) => row.id !== sourceRow2.id && row.status === TrafficSourceStatuses.connected);
68519
68727
  if (hasConnectedSibling) {
68520
68728
  throw validationError(
68521
68729
  `Traffic source "${sourceRow2.id}" is not authoritative. Activate it explicitly instead of resetting it.`
@@ -68532,7 +68740,7 @@ async function trafficRoutes(app, opts) {
68532
68740
  status: TrafficSourceStatuses.connected,
68533
68741
  lastError: null,
68534
68742
  updatedAt: now
68535
- }).where(eq90(trafficSources.id, sourceRow2.id)).run();
68743
+ }).where(eq91(trafficSources.id, sourceRow2.id)).run();
68536
68744
  writeAuditLog(tx, auditFromRequest(request, {
68537
68745
  projectId: project.id,
68538
68746
  actor: "api",
@@ -68540,7 +68748,7 @@ async function trafficRoutes(app, opts) {
68540
68748
  entityType: "traffic_source",
68541
68749
  entityId: sourceRow2.id
68542
68750
  }));
68543
- updatedRow = tx.select().from(trafficSources).where(eq90(trafficSources.id, sourceRow2.id)).get();
68751
+ updatedRow = tx.select().from(trafficSources).where(eq91(trafficSources.id, sourceRow2.id)).get();
68544
68752
  });
68545
68753
  return buildSourceDetail(project.id, updatedRow, new Date(Date.now() - 24 * 60 * 6e4).toISOString());
68546
68754
  }));
@@ -68552,7 +68760,7 @@ async function trafficRoutes(app, opts) {
68552
68760
  }
68553
68761
  let updatedRow;
68554
68762
  app.db.transaction((tx) => {
68555
- const sourceRow2 = tx.select().from(trafficSources).where(and79(eq90(trafficSources.projectId, project.id), eq90(trafficSources.id, request.params.id))).get();
68763
+ const sourceRow2 = tx.select().from(trafficSources).where(and80(eq91(trafficSources.projectId, project.id), eq91(trafficSources.id, request.params.id))).get();
68556
68764
  if (!sourceRow2) {
68557
68765
  throw notFound("traffic source", request.params.id);
68558
68766
  }
@@ -68565,7 +68773,7 @@ async function trafficRoutes(app, opts) {
68565
68773
  );
68566
68774
  }
68567
68775
  const now = (/* @__PURE__ */ new Date()).toISOString();
68568
- tx.update(trafficSources).set({ skippedThroughAt: null, updatedAt: now }).where(eq90(trafficSources.id, sourceRow2.id)).run();
68776
+ tx.update(trafficSources).set({ skippedThroughAt: null, updatedAt: now }).where(eq91(trafficSources.id, sourceRow2.id)).run();
68569
68777
  writeAuditLog(tx, auditFromRequest(request, {
68570
68778
  projectId: project.id,
68571
68779
  actor: "api",
@@ -68574,20 +68782,20 @@ async function trafficRoutes(app, opts) {
68574
68782
  entityId: sourceRow2.id,
68575
68783
  diff: { skippedThroughAt: sourceRow2.skippedThroughAt, sourceType: sourceRow2.sourceType }
68576
68784
  }));
68577
- updatedRow = tx.select().from(trafficSources).where(eq90(trafficSources.id, sourceRow2.id)).get();
68785
+ updatedRow = tx.select().from(trafficSources).where(eq91(trafficSources.id, sourceRow2.id)).get();
68578
68786
  });
68579
68787
  return buildSourceDetail(project.id, updatedRow, new Date(Date.now() - 24 * 60 * 6e4).toISOString());
68580
68788
  });
68581
68789
  app.get("/projects/:name/traffic/sources", async (request) => {
68582
68790
  const project = resolveProject(app.db, request.params.name);
68583
- const rows = app.db.select().from(trafficSources).where(eq90(trafficSources.projectId, project.id)).orderBy(desc41(trafficSources.createdAt)).all();
68791
+ const rows = app.db.select().from(trafficSources).where(eq91(trafficSources.projectId, project.id)).orderBy(desc41(trafficSources.createdAt)).all();
68584
68792
  const sources = rows.filter((row) => row.status !== TrafficSourceStatuses.archived).map(rowToDto);
68585
68793
  const response = { sources };
68586
68794
  return response;
68587
68795
  });
68588
68796
  app.get("/projects/:name/traffic/status", async (request) => {
68589
68797
  const project = resolveProject(app.db, request.params.name);
68590
- const rows = app.db.select().from(trafficSources).where(eq90(trafficSources.projectId, project.id)).orderBy(desc41(trafficSources.createdAt)).all();
68798
+ const rows = app.db.select().from(trafficSources).where(eq91(trafficSources.projectId, project.id)).orderBy(desc41(trafficSources.createdAt)).all();
68591
68799
  const since = new Date(Date.now() - 24 * 60 * 6e4).toISOString();
68592
68800
  const sources = rows.filter((row) => row.status !== TrafficSourceStatuses.archived).map((row) => buildSourceDetail(project.id, row, since));
68593
68801
  const response = { sources };
@@ -68597,7 +68805,7 @@ async function trafficRoutes(app, opts) {
68597
68805
  "/projects/:name/traffic/sources/:id",
68598
68806
  async (request) => {
68599
68807
  const project = resolveProject(app.db, request.params.name);
68600
- const row = app.db.select().from(trafficSources).where(eq90(trafficSources.id, request.params.id)).get();
68808
+ const row = app.db.select().from(trafficSources).where(eq91(trafficSources.id, request.params.id)).get();
68601
68809
  if (!row || row.projectId !== project.id) {
68602
68810
  throw notFound("Traffic source", request.params.id);
68603
68811
  }
@@ -68642,11 +68850,12 @@ async function trafficRoutes(app, opts) {
68642
68850
  const granularityParam = request.query?.granularity;
68643
68851
  let granularity = TrafficSeriesGranularities.hour;
68644
68852
  if (granularityParam !== void 0) {
68645
- if (granularityParam === TrafficSeriesGranularities.hour || granularityParam === TrafficSeriesGranularities.day) {
68646
- granularity = granularityParam;
68853
+ const parsed = trafficSeriesGranularitySchema.safeParse(granularityParam);
68854
+ if (parsed.success) {
68855
+ granularity = parsed.data;
68647
68856
  } else {
68648
68857
  throw validationError(
68649
- `"granularity" must be one of: ${TrafficSeriesGranularities.hour}, ${TrafficSeriesGranularities.day}`
68858
+ `"granularity" must be one of: ${trafficSeriesGranularitySchema.options.join(", ")}`
68650
68859
  );
68651
68860
  }
68652
68861
  }
@@ -68665,12 +68874,12 @@ async function trafficRoutes(app, opts) {
68665
68874
  const seriesByBucket = /* @__PURE__ */ new Map();
68666
68875
  if (kinds.has(TrafficEventKinds.crawler)) {
68667
68876
  const crawlerFilters = [
68668
- eq90(crawlerEventsHourly.projectId, project.id),
68877
+ eq91(crawlerEventsHourly.projectId, project.id),
68669
68878
  gte25(crawlerEventsHourly.tsHour, sinceIso),
68670
68879
  lte26(crawlerEventsHourly.tsHour, untilIso)
68671
68880
  ];
68672
- if (sourceIdParam) crawlerFilters.push(eq90(crawlerEventsHourly.sourceId, sourceIdParam));
68673
- const crawlerWhere = and79(...crawlerFilters);
68881
+ if (sourceIdParam) crawlerFilters.push(eq91(crawlerEventsHourly.sourceId, sourceIdParam));
68882
+ const crawlerWhere = and80(...crawlerFilters);
68674
68883
  const pathTotals = app.db.select({
68675
68884
  pathNormalized: crawlerEventsHourly.pathNormalized,
68676
68885
  hits: sql40`COALESCE(SUM(${crawlerEventsHourly.hits}), 0)`,
@@ -68681,7 +68890,7 @@ async function trafficRoutes(app, opts) {
68681
68890
  );
68682
68891
  crawlerTotal = crawlerSegments.content + crawlerSegments.sitemap + crawlerSegments.robots + crawlerSegments.asset + crawlerSegments.other;
68683
68892
  totalEventRows += pathTotals.reduce((sum, row) => sum + Number(row.rows), 0);
68684
- const crawlerSeriesBucket = granularity === TrafficSeriesGranularities.day ? sql40`substr(${crawlerEventsHourly.tsHour}, 1, 10)` : crawlerEventsHourly.tsHour;
68893
+ const crawlerSeriesBucket = seriesBucketSql(crawlerEventsHourly.tsHour, granularity);
68685
68894
  const distinctPaths = pathTotals.map((r) => ({ pathNormalized: r.pathNormalized }));
68686
68895
  const contentPaths = [];
68687
68896
  const infraPaths = [];
@@ -68740,19 +68949,19 @@ async function trafficRoutes(app, opts) {
68740
68949
  }
68741
68950
  if (kinds.has(TrafficEventKinds["ai-user-fetch"])) {
68742
68951
  const userFetchFilters = [
68743
- eq90(aiUserFetchEventsHourly.projectId, project.id),
68952
+ eq91(aiUserFetchEventsHourly.projectId, project.id),
68744
68953
  gte25(aiUserFetchEventsHourly.tsHour, sinceIso),
68745
68954
  lte26(aiUserFetchEventsHourly.tsHour, untilIso)
68746
68955
  ];
68747
- if (sourceIdParam) userFetchFilters.push(eq90(aiUserFetchEventsHourly.sourceId, sourceIdParam));
68748
- const userFetchWhere = and79(...userFetchFilters);
68956
+ if (sourceIdParam) userFetchFilters.push(eq91(aiUserFetchEventsHourly.sourceId, sourceIdParam));
68957
+ const userFetchWhere = and80(...userFetchFilters);
68749
68958
  const total = app.db.select({
68750
68959
  total: sql40`COALESCE(SUM(${aiUserFetchEventsHourly.hits}), 0)`,
68751
68960
  rows: sql40`COUNT(*)`
68752
68961
  }).from(aiUserFetchEventsHourly).where(userFetchWhere).get();
68753
68962
  aiUserFetchTotal = Number(total?.total ?? 0);
68754
68963
  totalEventRows += Number(total?.rows ?? 0);
68755
- const userFetchSeriesBucket = granularity === TrafficSeriesGranularities.day ? sql40`substr(${aiUserFetchEventsHourly.tsHour}, 1, 10)` : aiUserFetchEventsHourly.tsHour;
68964
+ const userFetchSeriesBucket = seriesBucketSql(aiUserFetchEventsHourly.tsHour, granularity);
68756
68965
  const userFetchSeries = app.db.select({
68757
68966
  bucket: userFetchSeriesBucket,
68758
68967
  hits: sql40`COALESCE(SUM(${aiUserFetchEventsHourly.hits}), 0)`
@@ -68779,12 +68988,12 @@ async function trafficRoutes(app, opts) {
68779
68988
  }
68780
68989
  if (kinds.has(TrafficEventKinds["ai-referral"])) {
68781
68990
  const aiFilters = [
68782
- eq90(aiReferralEventsHourly.projectId, project.id),
68991
+ eq91(aiReferralEventsHourly.projectId, project.id),
68783
68992
  gte25(aiReferralEventsHourly.tsHour, sinceIso),
68784
68993
  lte26(aiReferralEventsHourly.tsHour, untilIso)
68785
68994
  ];
68786
- if (sourceIdParam) aiFilters.push(eq90(aiReferralEventsHourly.sourceId, sourceIdParam));
68787
- const aiWhere = and79(...aiFilters);
68995
+ if (sourceIdParam) aiFilters.push(eq91(aiReferralEventsHourly.sourceId, sourceIdParam));
68996
+ const aiWhere = and80(...aiFilters);
68788
68997
  const total = app.db.select({
68789
68998
  total: sql40`COALESCE(SUM(${aiReferralEventsHourly.sessionsOrHits}), 0)`,
68790
68999
  nonhop: sql40`COALESCE(SUM(CASE WHEN ${referralLandedCondition()} THEN ${aiReferralEventsHourly.sessionsOrHits} ELSE 0 END), 0)`,
@@ -68802,7 +69011,7 @@ async function trafficRoutes(app, opts) {
68802
69011
  Number(total?.organic ?? 0)
68803
69012
  );
68804
69013
  totalEventRows += Number(total?.rows ?? 0);
68805
- const referralSeriesBucket = granularity === TrafficSeriesGranularities.day ? sql40`substr(${aiReferralEventsHourly.tsHour}, 1, 10)` : aiReferralEventsHourly.tsHour;
69014
+ const referralSeriesBucket = seriesBucketSql(aiReferralEventsHourly.tsHour, granularity);
68806
69015
  const referralSeries = app.db.select({
68807
69016
  bucket: referralSeriesBucket,
68808
69017
  hits: sql40`COALESCE(SUM(${aiReferralEventsHourly.sessionsOrHits}), 0)`,
@@ -68859,7 +69068,7 @@ async function trafficRoutes(app, opts) {
68859
69068
  windowEnd: untilIso,
68860
69069
  series: (() => {
68861
69070
  const points = completeTrafficSeries(since, until, granularity, seriesByBucket);
68862
- const coverageScope = (col, srcCol) => sourceIdParam ? and79(eq90(col, project.id), eq90(srcCol, sourceIdParam)) : eq90(col, project.id);
69071
+ const coverageScope = (col, srcCol) => sourceIdParam ? and80(eq91(col, project.id), eq91(srcCol, sourceIdParam)) : eq91(col, project.id);
68863
69072
  const firsts = [
68864
69073
  app.db.select({ v: sql40`MIN(${crawlerEventsHourly.tsHour})` }).from(crawlerEventsHourly).where(coverageScope(crawlerEventsHourly.projectId, crawlerEventsHourly.sourceId)).get()?.v,
68865
69074
  app.db.select({ v: sql40`MIN(${aiUserFetchEventsHourly.tsHour})` }).from(aiUserFetchEventsHourly).where(coverageScope(aiUserFetchEventsHourly.projectId, aiUserFetchEventsHourly.sourceId)).get()?.v,
@@ -68868,32 +69077,34 @@ async function trafficRoutes(app, opts) {
68868
69077
  const referralCountingChangedAt = aiReferralOlderCountUntil(app.db, { projectId: project.id, sourceId: sourceIdParam });
68869
69078
  const referralCountingChangedMs = referralCountingChangedAt === null ? null : Date.parse(referralCountingChangedAt);
68870
69079
  const coverageStart = firsts.length ? firsts.slice().sort()[0] : null;
68871
- const bucketStartMs = (bucket) => Date.parse(
68872
- granularity === TrafficSeriesGranularities.day ? `${bucket}T00:00:00.000Z` : bucket
68873
- );
68874
- const bucketMs = granularity === TrafficSeriesGranularities.day ? 864e5 : 36e5;
69080
+ const bucketStartMs = (bucket) => seriesBucketStartMs(bucket, granularity);
68875
69081
  const lastBucket = points.length ? points[points.length - 1].bucket : null;
68876
69082
  const lastBucketStartMs = lastBucket === null ? null : bucketStartMs(lastBucket);
68877
- const trailingBucketIsPartial = lastBucketStartMs !== null && Number.isFinite(lastBucketStartMs) && until.getTime() < lastBucketStartMs + bucketMs;
68878
- const coverageKey = coverageStart === null ? null : granularity === TrafficSeriesGranularities.day ? coverageStart.slice(0, 10) : coverageStart.slice(0, 13);
69083
+ const trailingBucketIsPartial = lastBucketStartMs !== null && Number.isFinite(lastBucketStartMs) && until.getTime() < nextSeriesBucketStartMs(lastBucketStartMs, granularity);
69084
+ const coverageKey = coverageStart === null ? null : seriesBucketOf(coverageStart, granularity);
68879
69085
  for (const pt of points) {
68880
- pt.measured = coverageKey === null ? false : (granularity === TrafficSeriesGranularities.day ? pt.bucket.slice(0, 10) : pt.bucket.slice(0, 13)) >= coverageKey;
69086
+ pt.measured = coverageKey === null ? false : seriesBucketOf(pt.bucket, granularity) >= coverageKey;
68881
69087
  }
68882
69088
  const firstMeasuredIdx = points.findIndex((pt) => pt.measured);
68883
69089
  const coverageStartMs = coverageStart === null ? null : Date.parse(coverageStart);
68884
- const leadingBucketIsPartial = firstMeasuredIdx >= 0 && coverageStartMs !== null && Number.isFinite(coverageStartMs) && coverageStartMs > bucketStartMs(points[firstMeasuredIdx].bucket);
69090
+ const leadingBucketIsPartial = firstMeasuredIdx >= 0 && coverageStartMs !== null && Number.isFinite(coverageStartMs) && Math.max(coverageStartMs, since.getTime()) > bucketStartMs(points[firstMeasuredIdx].bucket);
68885
69091
  return {
68886
69092
  granularity,
68887
69093
  points,
68888
69094
  coverageStart,
68889
69095
  aiReferralCountingChangedAt: referralCountingChangedAt,
69096
+ // The same two edges the fits leave out, named for a chart to draw as partial.
69097
+ partialBuckets: [
69098
+ ...firstMeasuredIdx >= 0 && leadingBucketIsPartial ? [points[firstMeasuredIdx].bucket] : [],
69099
+ ...trailingBucketIsPartial && lastBucket !== null && !(points.length - 1 === firstMeasuredIdx && leadingBucketIsPartial) ? [lastBucket] : []
69100
+ ],
68890
69101
  // Two edges are excluded from the fit, both for the same reason: a
68891
69102
  // bucket that is not a full reading must not be treated as one.
68892
69103
  //
68893
69104
  // Leading: unmeasured buckets predate recording, so they are the
68894
69105
  // ABSENCE of a reading, not a reading of zero. The first MEASURED
68895
- // bucket goes too when recording began inside it, since a fraction of
68896
- // a bucket read as a whole one slopes the fit up out of nothing.
69106
+ // bucket goes too when recording or the range began inside it, since
69107
+ // a fraction of a bucket read as a whole slopes the fit up out of nothing.
68897
69108
  //
68898
69109
  // Trailing: `until` defaults to now, so the newest bucket holds only
68899
69110
  // the elapsed fraction of the current period. A site flat at 500/day
@@ -68946,7 +69157,7 @@ async function trafficRoutes(app, opts) {
68946
69157
  }
68947
69158
 
68948
69159
  // ../api-routes/src/doctor/checks/report-sweeps.ts
68949
- import { and as and80, asc as asc24, eq as eq91, gte as gte26, lte as lte27 } from "drizzle-orm";
69160
+ import { and as and81, asc as asc24, eq as eq92, gte as gte26, lte as lte27 } from "drizzle-orm";
68950
69161
 
68951
69162
  // ../api-routes/src/visibility-attribution.ts
68952
69163
  function buildQueryAttribution2(projectQueries) {
@@ -68980,11 +69191,11 @@ function inspectSweep(ctx, run) {
68980
69191
  answerMentioned: querySnapshots.answerMentioned,
68981
69192
  answerText: querySnapshots.answerText,
68982
69193
  measurementExecutionId: querySnapshots.measurementExecutionId
68983
- }).from(querySnapshots).where(eq91(querySnapshots.runId, run.id)).all();
69194
+ }).from(querySnapshots).where(eq92(querySnapshots.runId, run.id)).all();
68984
69195
  if (snapshots.length === 0) return { detail: { ...base, complete: false, reason: "no-answers" } };
68985
69196
  try {
68986
69197
  if (run.measurementPlanVersionId) {
68987
- const version = ctx.db.select().from(measurementPlanVersions).where(and80(eq91(measurementPlanVersions.projectId, ctx.project.id), eq91(measurementPlanVersions.id, run.measurementPlanVersionId))).get();
69198
+ const version = ctx.db.select().from(measurementPlanVersions).where(and81(eq92(measurementPlanVersions.projectId, ctx.project.id), eq92(measurementPlanVersions.id, run.measurementPlanVersionId))).get();
68988
69199
  if (!version) return { detail: { ...base, complete: false, reason: "missing-plan" } };
68989
69200
  const plan = parseStoredMeasurementPlanAnyVersion(version.canonicalJson);
68990
69201
  const manifest = measurementRunExpectedSlots(run, plan);
@@ -69016,10 +69227,10 @@ function inspectSweep(ctx, run) {
69016
69227
  };
69017
69228
  return unbound ? { detail } : { detail, coverage: slotCoverage(expected2, answered3) };
69018
69229
  }
69019
- const stored = ctx.db.select().from(simpleMeasurementDefinitions).where(eq91(simpleMeasurementDefinitions.runId, run.id)).get();
69230
+ const stored = ctx.db.select().from(simpleMeasurementDefinitions).where(eq92(simpleMeasurementDefinitions.runId, run.id)).get();
69020
69231
  const frozen = stored ? simpleMeasurementDefinitionSchema.parse(stored.definition) : null;
69021
- const project = ctx.db.select().from(projects).where(eq91(projects.id, ctx.project.id)).get();
69022
- const basket = frozen ? frozen.queries.map((query2) => ({ id: query2.queryId, query: query2.queryText })) : ctx.db.select({ id: queries.id, query: queries.query }).from(queries).where(eq91(queries.projectId, project.id)).all();
69232
+ const project = ctx.db.select().from(projects).where(eq92(projects.id, ctx.project.id)).get();
69233
+ const basket = frozen ? frozen.queries.map((query2) => ({ id: query2.queryId, query: query2.queryText })) : ctx.db.select({ id: queries.id, query: queries.query }).from(queries).where(eq92(queries.projectId, project.id)).all();
69023
69234
  const providers = frozen ? frozen.engines.map((engine) => engine.provider) : project.providers.length > 0 ? project.providers : [...new Set(snapshots.map((snapshot) => snapshot.provider))];
69024
69235
  const attribution = buildQueryAttribution2(basket);
69025
69236
  const expected = new Set(basket.flatMap((query2) => providers.map((provider) => JSON.stringify([query2.id, provider.trim().toLowerCase()]))));
@@ -69068,7 +69279,7 @@ var reportSweepsCheck = {
69068
69279
  const graded = gradedReportMonths(reportMonths, now);
69069
69280
  const months = reportMonths.map((month) => {
69070
69281
  const bounds = calendarMonthBounds(month);
69071
- const monthRuns = ctx.db.select().from(runs).where(and80(eq91(runs.projectId, ctx.project.id), eq91(runs.kind, RunKinds["answer-visibility"]), notProbeRun(), gte26(runs.createdAt, bounds.since), lte27(runs.createdAt, bounds.until))).orderBy(asc24(runs.createdAt), asc24(runs.id)).all();
69282
+ const monthRuns = ctx.db.select().from(runs).where(and81(eq92(runs.projectId, ctx.project.id), eq92(runs.kind, RunKinds["answer-visibility"]), notProbeRun(), gte26(runs.createdAt, bounds.since), lte27(runs.createdAt, bounds.until))).orderBy(asc24(runs.createdAt), asc24(runs.id)).all();
69072
69283
  const evidence = monthRuns.map((run) => inspectSweep(ctx, run));
69073
69284
  return {
69074
69285
  month,
@@ -69091,7 +69302,7 @@ var reportSweepsCheck = {
69091
69302
  };
69092
69303
 
69093
69304
  // ../api-routes/src/doctor/checks/report-models.ts
69094
- import { and as and81, eq as eq92, gte as gte27, inArray as inArray42, lte as lte28 } from "drizzle-orm";
69305
+ import { and as and82, eq as eq93, gte as gte27, inArray as inArray42, lte as lte28 } from "drizzle-orm";
69095
69306
  var reportModelsCheck = {
69096
69307
  id: "report.models",
69097
69308
  category: CheckCategories.providers,
@@ -69106,7 +69317,7 @@ var reportModelsCheck = {
69106
69317
  const months = reportMonths.flatMap((month) => {
69107
69318
  const bounds = calendarMonthBounds(month);
69108
69319
  const previous = new Date(Date.parse(bounds.since) - 1).toISOString().slice(0, 7);
69109
- const observations = ctx.db.select({ provider: querySnapshots.provider, model: querySnapshots.model, at: runs.createdAt }).from(querySnapshots).innerJoin(runs, eq92(runs.id, querySnapshots.runId)).where(and81(eq92(runs.projectId, ctx.project.id), eq92(runs.kind, RunKinds["answer-visibility"]), inArray42(runs.status, [RunStatuses.completed, RunStatuses.partial]), notProbeRun(), gte27(runs.createdAt, bounds.since), lte28(runs.createdAt, bounds.until))).all();
69320
+ const observations = ctx.db.select({ provider: querySnapshots.provider, model: querySnapshots.model, at: runs.createdAt }).from(querySnapshots).innerJoin(runs, eq93(runs.id, querySnapshots.runId)).where(and82(eq93(runs.projectId, ctx.project.id), eq93(runs.kind, RunKinds["answer-visibility"]), inArray42(runs.status, [RunStatuses.completed, RunStatuses.partial]), notProbeRun(), gte27(runs.createdAt, bounds.since), lte28(runs.createdAt, bounds.until))).all();
69110
69321
  return readVisibilityContinuity(ctx.db, ctx.project.name, { from: previous, to: month }).map(({ frame, continuity: continuity2 }) => ({
69111
69322
  month,
69112
69323
  previousMonth: previous,
@@ -69147,7 +69358,7 @@ var reportModelsCheck = {
69147
69358
  };
69148
69359
 
69149
69360
  // ../api-routes/src/doctor/checks/report-daily-data.ts
69150
- import { eq as eq93 } from "drizzle-orm";
69361
+ import { eq as eq94 } from "drizzle-orm";
69151
69362
  var REPORTING_LAG_DAYS = 3;
69152
69363
  function dateSpan(dates) {
69153
69364
  const sorted = [...dates].sort();
@@ -69156,31 +69367,31 @@ function dateSpan(dates) {
69156
69367
  function latestSyncRange(ctx, source) {
69157
69368
  const projectId = ctx.project.id;
69158
69369
  if (source === "ga") {
69159
- const rows2 = ctx.db.select({ date: gaDailyTotals.date, syncedAt: gaDailyTotals.syncedAt }).from(gaDailyTotals).where(eq93(gaDailyTotals.projectId, projectId)).all();
69370
+ const rows2 = ctx.db.select({ date: gaDailyTotals.date, syncedAt: gaDailyTotals.syncedAt }).from(gaDailyTotals).where(eq94(gaDailyTotals.projectId, projectId)).all();
69160
69371
  const latest2 = rows2.reduce((max2, row) => max2 === null || row.syncedAt > max2 ? row.syncedAt : max2, null);
69161
- const summary3 = ctx.db.select({ start: gaTrafficSummaries.periodStart, end: gaTrafficSummaries.periodEnd, syncedAt: gaTrafficSummaries.syncedAt }).from(gaTrafficSummaries).where(eq93(gaTrafficSummaries.projectId, projectId)).get();
69372
+ const summary3 = ctx.db.select({ start: gaTrafficSummaries.periodStart, end: gaTrafficSummaries.periodEnd, syncedAt: gaTrafficSummaries.syncedAt }).from(gaTrafficSummaries).where(eq94(gaTrafficSummaries.projectId, projectId)).get();
69162
69373
  if (summary3 && (latest2 === null || summary3.syncedAt >= latest2)) return { start: summary3.start, end: summary3.end, syncedAt: summary3.syncedAt };
69163
69374
  const span2 = dateSpan(rows2.filter((row) => row.syncedAt === latest2).map((row) => row.date));
69164
69375
  return span2 && latest2 !== null ? { ...span2, syncedAt: latest2 } : null;
69165
69376
  }
69166
- const rows = ctx.db.select({ date: gscDailyTotals.date, createdAt: gscDailyTotals.createdAt }).from(gscDailyTotals).where(eq93(gscDailyTotals.projectId, projectId)).all();
69377
+ const rows = ctx.db.select({ date: gscDailyTotals.date, createdAt: gscDailyTotals.createdAt }).from(gscDailyTotals).where(eq94(gscDailyTotals.projectId, projectId)).all();
69167
69378
  const latest = rows.reduce((max2, row) => max2 === null || row.createdAt > max2 ? row.createdAt : max2, null);
69168
69379
  const span = dateSpan(rows.filter((row) => row.createdAt === latest).map((row) => row.date));
69169
69380
  if (!span || latest === null) return null;
69170
- const watermark = ctx.db.select({ through: gscDataWatermarks.syncedThroughDate, updatedAt: gscDataWatermarks.updatedAt }).from(gscDataWatermarks).where(eq93(gscDataWatermarks.projectId, projectId)).get();
69381
+ const watermark = ctx.db.select({ through: gscDataWatermarks.syncedThroughDate, updatedAt: gscDataWatermarks.updatedAt }).from(gscDataWatermarks).where(eq94(gscDataWatermarks.projectId, projectId)).get();
69171
69382
  return watermark?.through && watermark.updatedAt === latest && watermark.through > span.end ? { start: span.start, end: watermark.through, syncedAt: latest } : { ...span, syncedAt: latest };
69172
69383
  }
69173
69384
  function dailyCoverage(ctx, month, source) {
69174
69385
  const project = ctx.project;
69175
69386
  const connection = source === "ga" ? ctx.ga4CredentialStore?.getConnection(project.name) ?? ctx.googleConnectionStore?.getConnection(project.canonicalDomain, "ga4") : ctx.googleConnectionStore?.getConnection(project.canonicalDomain, "gsc");
69176
- const stored = source === "ga" ? ctx.db.select({ date: gaDailyTotals.date, count: gaDailyTotals.sessions }).from(gaDailyTotals).where(eq93(gaDailyTotals.projectId, project.id)).all() : ctx.db.select({ date: gscDailyTotals.date, count: gscDailyTotals.impressions }).from(gscDailyTotals).where(eq93(gscDailyTotals.projectId, project.id)).all();
69387
+ const stored = source === "ga" ? ctx.db.select({ date: gaDailyTotals.date, count: gaDailyTotals.sessions }).from(gaDailyTotals).where(eq94(gaDailyTotals.projectId, project.id)).all() : ctx.db.select({ date: gscDailyTotals.date, count: gscDailyTotals.impressions }).from(gscDailyTotals).where(eq94(gscDailyTotals.projectId, project.id)).all();
69177
69388
  if (!connection && stored.length === 0) return null;
69178
69389
  const bounds = calendarMonthBounds(month);
69179
69390
  const start = bounds.since.slice(0, 10);
69180
69391
  const end = bounds.until.slice(0, 10);
69181
69392
  const timeZone = source === "gsc" ? "America/Los_Angeles" : "UTC";
69182
69393
  const matureThrough = isoDateDaysBeforeInTimeZone((/* @__PURE__ */ new Date()).toISOString(), REPORTING_LAG_DAYS, timeZone);
69183
- const created = ctx.db.select({ date: projects.createdAt }).from(projects).where(eq93(projects.id, project.id)).get().date.slice(0, 10);
69394
+ const created = ctx.db.select({ date: projects.createdAt }).from(projects).where(eq94(projects.id, project.id)).get().date.slice(0, 10);
69184
69395
  const connectedAt = connection?.createdAt?.slice(0, 10) ?? created;
69185
69396
  const onboardingDate = [connectedAt, ...stored.map((row) => row.date)].sort()[0];
69186
69397
  const byDate = new Map(stored.map((row) => [row.date, row.count]));
@@ -69262,7 +69473,7 @@ var reportDailyDataCheck = {
69262
69473
  };
69263
69474
 
69264
69475
  // ../api-routes/src/doctor/checks/referral-assessment.ts
69265
- import { and as and82, eq as eq94, ne as ne16 } from "drizzle-orm";
69476
+ import { and as and83, eq as eq95, ne as ne16 } from "drizzle-orm";
69266
69477
  var REFERRAL_ASSESSMENT_CHECKS = [{
69267
69478
  id: "report.ai-referral-bursts",
69268
69479
  category: CheckCategories.integrations,
@@ -69271,7 +69482,7 @@ var REFERRAL_ASSESSMENT_CHECKS = [{
69271
69482
  notificationPolicy: CheckNotificationPolicies.silent,
69272
69483
  run: (ctx) => {
69273
69484
  if (!ctx.project) return { status: CheckStatuses.skipped, code: "report.ai-referral-bursts.no-project", summary: "Select a project to inspect referral evidence." };
69274
- const configured = ctx.db.select({ id: trafficSources.id }).from(trafficSources).where(and82(eq94(trafficSources.projectId, ctx.project.id), ne16(trafficSources.status, TrafficSourceStatuses.archived))).get();
69485
+ const configured = ctx.db.select({ id: trafficSources.id }).from(trafficSources).where(and83(eq95(trafficSources.projectId, ctx.project.id), ne16(trafficSources.status, TrafficSourceStatuses.archived))).get();
69275
69486
  if (!configured) {
69276
69487
  return { status: CheckStatuses.skipped, code: "report.ai-referral-bursts.not-configured", summary: "No non-archived server traffic source is configured for this project." };
69277
69488
  }
@@ -69683,17 +69894,17 @@ var BACKLINKS_CHECKS = [
69683
69894
  ];
69684
69895
 
69685
69896
  // ../api-routes/src/doctor/checks/bing-auth.ts
69686
- import { and as and83, desc as desc42, eq as eq95, inArray as inArray43 } from "drizzle-orm";
69897
+ import { and as and84, desc as desc42, eq as eq96, inArray as inArray43 } from "drizzle-orm";
69687
69898
  var BING_CONNECTION_ENTITY = "bing_connection";
69688
69899
  var BING_CONNECTED_ACTION = "bing.connected";
69689
69900
  var BING_DISCONNECTED_ACTION = "bing.disconnected";
69690
69901
  function lostBingConnection(ctx, project) {
69691
69902
  const hasRow = (row) => row !== void 0;
69692
- const storedData = hasRow(ctx.db.select({ id: bingCoverageSnapshots.id }).from(bingCoverageSnapshots).where(eq95(bingCoverageSnapshots.projectId, project.id)).limit(1).get()) || hasRow(ctx.db.select({ id: bingUrlInspections.id }).from(bingUrlInspections).where(eq95(bingUrlInspections.projectId, project.id)).limit(1).get()) || hasRow(ctx.db.select({ id: bingKeywordStats.id }).from(bingKeywordStats).where(eq95(bingKeywordStats.projectId, project.id)).limit(1).get()) || hasRow(ctx.db.select({ id: bingTrafficDaily.id }).from(bingTrafficDaily).where(eq95(bingTrafficDaily.projectId, project.id)).limit(1).get());
69903
+ const storedData = hasRow(ctx.db.select({ id: bingCoverageSnapshots.id }).from(bingCoverageSnapshots).where(eq96(bingCoverageSnapshots.projectId, project.id)).limit(1).get()) || hasRow(ctx.db.select({ id: bingUrlInspections.id }).from(bingUrlInspections).where(eq96(bingUrlInspections.projectId, project.id)).limit(1).get()) || hasRow(ctx.db.select({ id: bingKeywordStats.id }).from(bingKeywordStats).where(eq96(bingKeywordStats.projectId, project.id)).limit(1).get()) || hasRow(ctx.db.select({ id: bingTrafficDaily.id }).from(bingTrafficDaily).where(eq96(bingTrafficDaily.projectId, project.id)).limit(1).get());
69693
69904
  if (!storedData) return false;
69694
- const lastChange = ctx.db.select({ action: auditLog.action }).from(auditLog).where(and83(
69695
- eq95(auditLog.entityType, BING_CONNECTION_ENTITY),
69696
- eq95(auditLog.entityId, project.canonicalDomain),
69905
+ const lastChange = ctx.db.select({ action: auditLog.action }).from(auditLog).where(and84(
69906
+ eq96(auditLog.entityType, BING_CONNECTION_ENTITY),
69907
+ eq96(auditLog.entityId, project.canonicalDomain),
69697
69908
  inArray43(auditLog.action, [BING_CONNECTED_ACTION, BING_DISCONNECTED_ACTION])
69698
69909
  )).orderBy(desc42(auditLog.createdAt)).limit(1).get();
69699
69910
  return lastChange?.action !== BING_DISCONNECTED_ACTION;
@@ -69852,7 +70063,7 @@ var BING_AUTH_CHECKS = [
69852
70063
  ];
69853
70064
 
69854
70065
  // ../api-routes/src/doctor/checks/content.ts
69855
- import { eq as eq96 } from "drizzle-orm";
70066
+ import { eq as eq97 } from "drizzle-orm";
69856
70067
  var WINNABILITY_COVERAGE_WARN_THRESHOLD = 0.8;
69857
70068
  var UNCLASSIFIED_DOMAIN_SAMPLE_LIMIT = 10;
69858
70069
  function skippedNoProject2() {
@@ -69865,7 +70076,7 @@ function skippedNoProject2() {
69865
70076
  }
69866
70077
  function loadProject(ctx) {
69867
70078
  if (!ctx.project) return null;
69868
- return ctx.db.select().from(projects).where(eq96(projects.id, ctx.project.id)).get() ?? null;
70079
+ return ctx.db.select().from(projects).where(eq97(projects.id, ctx.project.id)).get() ?? null;
69869
70080
  }
69870
70081
  var winnabilityCoverageCheck = {
69871
70082
  id: "content.winnability.coverage",
@@ -69958,7 +70169,7 @@ var CONTENT_CHECK_BY_ID = Object.fromEntries(
69958
70169
  );
69959
70170
 
69960
70171
  // ../api-routes/src/doctor/checks/ads.ts
69961
- import { eq as eq97 } from "drizzle-orm";
70172
+ import { eq as eq98 } from "drizzle-orm";
69962
70173
  var ADS_RECENT_SYNC_WARN_DAYS = 7;
69963
70174
  var ADS_RECENT_SYNC_FAIL_DAYS = 30;
69964
70175
  var adsConnectionCheck = {
@@ -69975,7 +70186,7 @@ var adsConnectionCheck = {
69975
70186
  remediation: null
69976
70187
  };
69977
70188
  }
69978
- const row = ctx.db.select().from(adsConnections).where(eq97(adsConnections.projectId, ctx.project.id)).get();
70189
+ const row = ctx.db.select().from(adsConnections).where(eq98(adsConnections.projectId, ctx.project.id)).get();
69979
70190
  if (!row) {
69980
70191
  return {
69981
70192
  status: CheckStatuses.skipped,
@@ -70025,7 +70236,7 @@ var adsRecentSyncCheck = {
70025
70236
  remediation: null
70026
70237
  };
70027
70238
  }
70028
- const row = ctx.db.select().from(adsConnections).where(eq97(adsConnections.projectId, ctx.project.id)).get();
70239
+ const row = ctx.db.select().from(adsConnections).where(eq98(adsConnections.projectId, ctx.project.id)).get();
70029
70240
  if (!row) {
70030
70241
  return {
70031
70242
  status: CheckStatuses.skipped,
@@ -70240,7 +70451,7 @@ var ga4ConnectionCheck = {
70240
70451
  var GA_AUTH_CHECKS = [ga4ConnectionCheck];
70241
70452
 
70242
70453
  // ../api-routes/src/doctor/checks/data-freshness.ts
70243
- import { and as and84, desc as desc43, eq as eq98, inArray as inArray44 } from "drizzle-orm";
70454
+ import { and as and85, desc as desc43, eq as eq99, inArray as inArray44 } from "drizzle-orm";
70244
70455
  var GA_DATA_AGING_DAYS = 3;
70245
70456
  var GA_DATA_STALE_DAYS = 5;
70246
70457
  var GSC_DATA_AGING_DAYS = 5;
@@ -70270,7 +70481,7 @@ function skipped(code, summary3) {
70270
70481
  return { status: CheckStatuses.skipped, code, summary: summary3, remediation: null };
70271
70482
  }
70272
70483
  function lastSyncAgeDays(ctx, kinds) {
70273
- const row = ctx.db.select({ createdAt: runs.createdAt }).from(runs).where(and84(eq98(runs.projectId, ctx.project.id), inArray44(runs.kind, kinds), eq98(runs.status, "completed"))).orderBy(desc43(runs.createdAt)).limit(1).get();
70484
+ const row = ctx.db.select({ createdAt: runs.createdAt }).from(runs).where(and85(eq99(runs.projectId, ctx.project.id), inArray44(runs.kind, kinds), eq99(runs.status, "completed"))).orderBy(desc43(runs.createdAt)).limit(1).get();
70274
70485
  if (!row?.createdAt) return null;
70275
70486
  const age = Math.floor((Date.now() - Date.parse(row.createdAt)) / DAY_MS6);
70276
70487
  return Number.isFinite(age) ? age : null;
@@ -70372,7 +70583,7 @@ var gaRecentDataCheck = {
70372
70583
  const connection = gaConnected(ctx);
70373
70584
  if (connection === "store-unavailable") return skipped("ga.data.store-unavailable", "No GA4 credential store configured for this deployment.");
70374
70585
  if (connection === "not-connected") return skipped("ga.data.not-connected", "GA4 is not connected for this project.");
70375
- const newest = ctx.db.select({ date: gaDailyTotals.date }).from(gaDailyTotals).where(eq98(gaDailyTotals.projectId, ctx.project.id)).orderBy(desc43(gaDailyTotals.date)).limit(1).get();
70586
+ const newest = ctx.db.select({ date: gaDailyTotals.date }).from(gaDailyTotals).where(eq99(gaDailyTotals.projectId, ctx.project.id)).orderBy(desc43(gaDailyTotals.date)).limit(1).get();
70376
70587
  return grade(ctx, GA_SPEC, newest?.date);
70377
70588
  }
70378
70589
  };
@@ -70387,16 +70598,16 @@ var gscRecentDataCheck = {
70387
70598
  if (!ctx.googleConnectionStore.getConnection(ctx.project.canonicalDomain, "gsc")) {
70388
70599
  return skipped("gsc.data.not-connected", "Search Console is not connected for this project.");
70389
70600
  }
70390
- const watermark = ctx.db.select({ dataThroughDate: gscDataWatermarks.dataThroughDate }).from(gscDataWatermarks).where(eq98(gscDataWatermarks.projectId, ctx.project.id)).get();
70601
+ const watermark = ctx.db.select({ dataThroughDate: gscDataWatermarks.dataThroughDate }).from(gscDataWatermarks).where(eq99(gscDataWatermarks.projectId, ctx.project.id)).get();
70391
70602
  if (watermark?.dataThroughDate) return grade(ctx, GSC_SPEC, watermark.dataThroughDate);
70392
- const newest = ctx.db.select({ date: gscDailyTotals.date }).from(gscDailyTotals).where(eq98(gscDailyTotals.projectId, ctx.project.id)).orderBy(desc43(gscDailyTotals.date)).limit(1).get();
70603
+ const newest = ctx.db.select({ date: gscDailyTotals.date }).from(gscDailyTotals).where(eq99(gscDailyTotals.projectId, ctx.project.id)).orderBy(desc43(gscDailyTotals.date)).limit(1).get();
70393
70604
  return grade(ctx, GSC_SPEC, newest?.date);
70394
70605
  }
70395
70606
  };
70396
70607
  var DATA_FRESHNESS_CHECKS = [gaRecentDataCheck, gscRecentDataCheck];
70397
70608
 
70398
70609
  // ../api-routes/src/doctor/checks/seo-refresh.ts
70399
- import { and as and85, asc as asc25, desc as desc44, eq as eq99, gt as gt8, inArray as inArray45, sql as sql41 } from "drizzle-orm";
70610
+ import { and as and86, asc as asc25, desc as desc44, eq as eq100, gt as gt8, inArray as inArray45, sql as sql41 } from "drizzle-orm";
70400
70611
  var GRACE_DAYS = 2;
70401
70612
  var DAY_MS7 = 24 * 60 * 60 * 1e3;
70402
70613
  var FIRING_SPAN_MS = 6e4;
@@ -70405,7 +70616,7 @@ function skipped2(code, summary3) {
70405
70616
  return { status: CheckStatuses.skipped, code, summary: summary3, remediation: null };
70406
70617
  }
70407
70618
  function seoSyncRuns(projectId) {
70408
- return and85(eq99(runs.projectId, projectId), eq99(runs.kind, RunKinds["seo-sync"]));
70619
+ return and86(eq100(runs.projectId, projectId), eq100(runs.kind, RunKinds["seo-sync"]));
70409
70620
  }
70410
70621
  function scheduleTiming(schedule) {
70411
70622
  if (!schedule.recurrence) return { cronExpr: schedule.cronExpr, timezone: schedule.timezone, recurrence: null };
@@ -70413,16 +70624,16 @@ function scheduleTiming(schedule) {
70413
70624
  return recurrence ? { cronExpr: schedule.cronExpr, timezone: schedule.timezone, recurrence } : null;
70414
70625
  }
70415
70626
  function latestFiring(ctx, projectId, enabledSince) {
70416
- const scheduled = and85(seoSyncRuns(projectId), eq99(runs.trigger, RunTriggers.scheduled), gt8(runs.createdAt, enabledSince));
70627
+ const scheduled = and86(seoSyncRuns(projectId), eq100(runs.trigger, RunTriggers.scheduled), gt8(runs.createdAt, enabledSince));
70417
70628
  const newest = ctx.db.select({ createdAt: runs.createdAt }).from(runs).where(scheduled).orderBy(desc44(runs.createdAt)).limit(1).get();
70418
70629
  if (!newest) return [];
70419
70630
  const newestMs = Date.parse(newest.createdAt);
70420
70631
  const after = Number.isFinite(newestMs) ? new Date(newestMs - FIRING_SPAN_MS).toISOString() : newest.createdAt;
70421
- return ctx.db.select({ id: runs.id, status: runs.status, error: runs.error, createdAt: runs.createdAt }).from(runs).where(and85(scheduled, gt8(runs.createdAt, after))).orderBy(asc25(runs.createdAt), asc25(runs.id)).all();
70632
+ return ctx.db.select({ id: runs.id, status: runs.status, error: runs.error, createdAt: runs.createdAt }).from(runs).where(and86(scheduled, gt8(runs.createdAt, after))).orderBy(asc25(runs.createdAt), asc25(runs.id)).all();
70422
70633
  }
70423
70634
  function lastCompleted(ctx, projectId) {
70424
70635
  const at = sql41`coalesce(${runs.finishedAt}, ${runs.createdAt})`;
70425
- return ctx.db.select({ id: runs.id, at }).from(runs).where(and85(seoSyncRuns(projectId), eq99(runs.status, RunStatuses.completed))).orderBy(desc44(at)).limit(1).get() ?? null;
70636
+ return ctx.db.select({ id: runs.id, at }).from(runs).where(and86(seoSyncRuns(projectId), eq100(runs.status, RunStatuses.completed))).orderBy(desc44(at)).limit(1).get() ?? null;
70426
70637
  }
70427
70638
  function isIncomplete(run) {
70428
70639
  return run.status === RunStatuses.failed || run.status === RunStatuses.partial;
@@ -70436,7 +70647,7 @@ function unsettledRuns(ctx, projectId, firing, profiles) {
70436
70647
  const incomplete = firing.filter(isIncomplete);
70437
70648
  const firedAt = firing[firing.length - 1]?.createdAt;
70438
70649
  if (incomplete.length === 0 || !firedAt) return incomplete;
70439
- const refreshed = new Set(ctx.db.select({ profileId: seoJobs.profileId }).from(runs).innerJoin(seoJobs, eq99(seoJobs.runId, runs.id)).where(and85(seoSyncRuns(projectId), eq99(runs.status, RunStatuses.completed), gt8(runs.createdAt, firedAt))).all().map((row) => row.profileId));
70650
+ const refreshed = new Set(ctx.db.select({ profileId: seoJobs.profileId }).from(runs).innerJoin(seoJobs, eq100(seoJobs.runId, runs.id)).where(and86(seoSyncRuns(projectId), eq100(runs.status, RunStatuses.completed), gt8(runs.createdAt, firedAt))).all().map((row) => row.profileId));
70440
70651
  return incomplete.filter((run) => {
70441
70652
  const profile = profiles.get(run.id) ?? null;
70442
70653
  return profile === null ? refreshed.size === 0 : !refreshed.has(profile);
@@ -70446,7 +70657,7 @@ var COUNT_LIST = new Intl.ListFormat("en", { style: "long", type: "conjunction"
70446
70657
  function refreshReason(ctx, run) {
70447
70658
  const stored = parseRunError(run.error);
70448
70659
  if (stored) return formatRunErrorOneLine(stored);
70449
- const tasks = currentSeoTasks(ctx.db.select({ id: seoTasks.id, retryOf: seoTasks.retryOf, status: seoTasks.status, error: seoTasks.error, request: seoTasks.request }).from(seoTasks).innerJoin(seoJobs, eq99(seoTasks.jobId, seoJobs.id)).where(eq99(seoJobs.runId, run.id)).all());
70660
+ const tasks = currentSeoTasks(ctx.db.select({ id: seoTasks.id, retryOf: seoTasks.retryOf, status: seoTasks.status, error: seoTasks.error, request: seoTasks.request }).from(seoTasks).innerJoin(seoJobs, eq100(seoTasks.jobId, seoJobs.id)).where(eq100(seoJobs.runId, run.id)).all());
70450
70661
  const failed = tasks.filter((task) => task.status === SeoRunStatuses.failed || task.status === SeoRunStatuses["reconciliation-required"]);
70451
70662
  const partial = tasks.filter((task) => task.status === SeoRunStatuses.partial).length;
70452
70663
  const cancelled = tasks.filter((task) => task.status === SeoRunStatuses.cancelled).length;
@@ -70466,7 +70677,7 @@ function refreshReason(ctx, run) {
70466
70677
  return `Of ${tasks.length} ${unit}, ${COUNT_LIST.format(counts2)}.${errors ? ` ${errors}` : ""}`;
70467
70678
  }
70468
70679
  function gradeSeoRefresh(ctx, project) {
70469
- const schedule = ctx.db.select().from(schedules).where(and85(eq99(schedules.projectId, project.id), eq99(schedules.kind, SchedulableRunKinds["seo-sync"]))).get();
70680
+ const schedule = ctx.db.select().from(schedules).where(and86(eq100(schedules.projectId, project.id), eq100(schedules.kind, SchedulableRunKinds["seo-sync"]))).get();
70470
70681
  if (!schedule) return skipped2("seo.data.not-scheduled", "No SEO refresh schedule is set for this project.");
70471
70682
  if (!schedule.enabled) return skipped2("seo.data.not-scheduled", "The SEO refresh schedule is disabled.");
70472
70683
  const name = project.name;
@@ -70538,7 +70749,7 @@ function gradeSeoRefresh(ctx, project) {
70538
70749
  };
70539
70750
  }
70540
70751
  if (now >= Date.parse(deadlineAt)) {
70541
- const active = ctx.db.select({ id: runs.id, createdAt: runs.createdAt }).from(runs).where(and85(seoSyncRuns(project.id), inArray45(runs.status, [RunStatuses.queued, RunStatuses.running]))).orderBy(asc25(runs.createdAt)).limit(1).get();
70752
+ const active = ctx.db.select({ id: runs.id, createdAt: runs.createdAt }).from(runs).where(and86(seoSyncRuns(project.id), inArray45(runs.status, [RunStatuses.queued, RunStatuses.running]))).orderBy(asc25(runs.createdAt)).limit(1).get();
70542
70753
  const since = last ? `No SEO refresh has completed since ${day(last.at)}.` : "No SEO refresh has completed since the schedule was enabled.";
70543
70754
  const missed = last ? `The scheduled refresh due ${day(dueAt)} did not complete within ${GRACE_DAYS} days.` : `Its first refresh, due ${day(dueAt)}, did not complete within ${GRACE_DAYS} days.`;
70544
70755
  return {
@@ -70848,7 +71059,7 @@ var siteReachabilityCheck = {
70848
71059
  var SITE_REACHABILITY_CHECKS = [siteReachabilityCheck];
70849
71060
 
70850
71061
  // ../api-routes/src/doctor/checks/api-hosts.ts
70851
- import { eq as eq101 } from "drizzle-orm";
71062
+ import { eq as eq102 } from "drizzle-orm";
70852
71063
 
70853
71064
  // ../api-routes/src/api-host-probe.ts
70854
71065
  import dns4 from "dns/promises";
@@ -70954,7 +71165,7 @@ async function probeApiHost(host, options = {}) {
70954
71165
  }
70955
71166
 
70956
71167
  // ../api-routes/src/doctor/checks/integration-sync.ts
70957
- import { and as and86, count as count7, desc as desc45, eq as eq100, gte as gte28, gt as gt9, inArray as inArray46, isNull as isNull14, notInArray as notInArray3, or as or19 } from "drizzle-orm";
71168
+ import { and as and87, count as count7, desc as desc45, eq as eq101, gte as gte28, gt as gt9, inArray as inArray46, isNull as isNull14, notInArray as notInArray3, or as or19 } from "drizzle-orm";
70958
71169
  var SYNC_FAILURE_WINDOW = 3;
70959
71170
  var SUMMARY_ERROR_LIMIT = 300;
70960
71171
  var FINISHED_STATUSES = [RunStatuses.completed, RunStatuses.partial, RunStatuses.failed];
@@ -70981,7 +71192,7 @@ function gscConnection(ctx, project) {
70981
71192
  function gbpConnection(ctx, project) {
70982
71193
  if (!ctx.googleConnectionStore) return { state: "store-unavailable" };
70983
71194
  if (!ctx.googleConnectionStore.getConnection(project.canonicalDomain, "gbp")) return { state: "not-connected" };
70984
- const selected = ctx.db.select({ locationName: gbpLocations.locationName }).from(gbpLocations).where(and86(eq100(gbpLocations.projectId, project.id), eq100(gbpLocations.selected, true))).limit(1).get();
71195
+ const selected = ctx.db.select({ locationName: gbpLocations.locationName }).from(gbpLocations).where(and87(eq101(gbpLocations.projectId, project.id), eq101(gbpLocations.selected, true))).limit(1).get();
70985
71196
  return selected ? CONNECTED : { state: "no-selected-locations" };
70986
71197
  }
70987
71198
  function gaConnection(ctx, project) {
@@ -71054,16 +71265,16 @@ function latestErrorText(raw) {
71054
71265
  return parsed ? formatRunErrorOneLine(parsed) : "no error was recorded";
71055
71266
  }
71056
71267
  function readSyncRunState(db, projectId, kind, options = {}) {
71057
- const scope = and86(
71058
- eq100(runs.projectId, projectId),
71059
- eq100(runs.kind, kind),
71060
- ...options.sourceId ? [eq100(runs.sourceId, options.sourceId)] : [],
71268
+ const scope = and87(
71269
+ eq101(runs.projectId, projectId),
71270
+ eq101(runs.kind, kind),
71271
+ ...options.sourceId ? [eq101(runs.sourceId, options.sourceId)] : [],
71061
71272
  ...options.nothingToSyncErrors?.length ? [or19(isNull14(runs.error), notInArray3(runs.error, [...options.nothingToSyncErrors]))] : []
71062
71273
  );
71063
- const graded = options.since ? and86(scope, gte28(runs.createdAt, options.since)) : scope;
71064
- const recent = db.select({ id: runs.id, status: runs.status, trigger: runs.trigger, error: runs.error, createdAt: runs.createdAt }).from(runs).where(and86(graded, inArray46(runs.status, FINISHED_STATUSES))).orderBy(desc45(runs.createdAt)).limit(SYNC_FAILURE_WINDOW).all();
71065
- const lastSuccess = db.select({ createdAt: runs.createdAt, finishedAt: runs.finishedAt }).from(runs).where(and86(graded, inArray46(runs.status, SUCCESS_STATUSES))).orderBy(desc45(runs.createdAt)).limit(1).get();
71066
- const failureCount = db.select({ count: count7() }).from(runs).where(and86(lastSuccess ? and86(graded, gt9(runs.createdAt, lastSuccess.createdAt)) : graded, eq100(runs.status, RunStatuses.failed))).get();
71274
+ const graded = options.since ? and87(scope, gte28(runs.createdAt, options.since)) : scope;
71275
+ const recent = db.select({ id: runs.id, status: runs.status, trigger: runs.trigger, error: runs.error, createdAt: runs.createdAt }).from(runs).where(and87(graded, inArray46(runs.status, FINISHED_STATUSES))).orderBy(desc45(runs.createdAt)).limit(SYNC_FAILURE_WINDOW).all();
71276
+ const lastSuccess = db.select({ createdAt: runs.createdAt, finishedAt: runs.finishedAt }).from(runs).where(and87(graded, inArray46(runs.status, SUCCESS_STATUSES))).orderBy(desc45(runs.createdAt)).limit(1).get();
71277
+ const failureCount = db.select({ count: count7() }).from(runs).where(and87(lastSuccess ? and87(graded, gt9(runs.createdAt, lastSuccess.createdAt)) : graded, eq101(runs.status, RunStatuses.failed))).get();
71067
71278
  const failuresSinceSuccess = Number(failureCount?.count ?? 0);
71068
71279
  const latest = recent[0];
71069
71280
  return {
@@ -71160,7 +71371,7 @@ function connectionFor(prefix, ctx, project) {
71160
71371
  if (prefix === "google-ads") return googleMarketingConnection("googleAds")(ctx);
71161
71372
  if (prefix === "gtm") return googleMarketingConnection("gtm")(ctx);
71162
71373
  if (prefix === "bing") return ctx.bingConnectionStore?.getConnection(project.canonicalDomain) ? { state: "connected", since: null } : { state: "not-connected" };
71163
- return ctx.db.select({ id: adsConnections.id }).from(adsConnections).where(eq101(adsConnections.projectId, project.id)).limit(1).get() ? { state: "connected", since: null } : { state: "not-connected" };
71374
+ return ctx.db.select({ id: adsConnections.id }).from(adsConnections).where(eq102(adsConnections.projectId, project.id)).limit(1).get() ? { state: "connected", since: null } : { state: "not-connected" };
71164
71375
  }
71165
71376
  function syncCommand(prefix) {
71166
71377
  if (prefix === "gsc") return "google sync";
@@ -71205,7 +71416,7 @@ function definition(prefix) {
71205
71416
  var API_HOST_CHECKS = Object.keys(API_HOST_TABLE).map(definition);
71206
71417
 
71207
71418
  // ../api-routes/src/doctor/checks/gbp-auth.ts
71208
- import { and as and87, eq as eq102 } from "drizzle-orm";
71419
+ import { and as and88, eq as eq103 } from "drizzle-orm";
71209
71420
  var RECENT_SYNC_WARN_DAYS = 4;
71210
71421
  var RECENT_SYNC_FAIL_DAYS = 30;
71211
71422
  function skippedNoProject3() {
@@ -71225,7 +71436,7 @@ function storeUnavailable() {
71225
71436
  };
71226
71437
  }
71227
71438
  function hasSelectedLocation(ctx, projectId) {
71228
- return !!ctx.db.select({ id: gbpLocations.id }).from(gbpLocations).where(and87(eq102(gbpLocations.projectId, projectId), eq102(gbpLocations.selected, true))).limit(1).get();
71439
+ return !!ctx.db.select({ id: gbpLocations.id }).from(gbpLocations).where(and88(eq103(gbpLocations.projectId, projectId), eq103(gbpLocations.selected, true))).limit(1).get();
71229
71440
  }
71230
71441
  async function resolveGbpToken(ctx) {
71231
71442
  if (!ctx.project) return { ok: false, output: skippedNoProject3() };
@@ -71485,7 +71696,7 @@ var recentSyncCheck = {
71485
71696
  title: "GBP recent sync",
71486
71697
  run: (ctx) => {
71487
71698
  if (!ctx.project) return skippedNoProject3();
71488
- const selected = ctx.db.select({ locationName: gbpLocations.locationName, syncedAt: gbpLocations.syncedAt }).from(gbpLocations).where(and87(eq102(gbpLocations.projectId, ctx.project.id), eq102(gbpLocations.selected, true))).all();
71699
+ const selected = ctx.db.select({ locationName: gbpLocations.locationName, syncedAt: gbpLocations.syncedAt }).from(gbpLocations).where(and88(eq103(gbpLocations.projectId, ctx.project.id), eq103(gbpLocations.selected, true))).all();
71489
71700
  if (selected.length === 0) {
71490
71701
  return {
71491
71702
  status: CheckStatuses.skipped,
@@ -71545,7 +71756,7 @@ var GBP_AUTH_CHECK_BY_ID = Object.fromEntries(
71545
71756
  );
71546
71757
 
71547
71758
  // ../api-routes/src/doctor/checks/places.ts
71548
- import { eq as eq103 } from "drizzle-orm";
71759
+ import { eq as eq104 } from "drizzle-orm";
71549
71760
  var apiKeyCheck = {
71550
71761
  id: "gbp.places.api-key",
71551
71762
  category: CheckCategories.auth,
@@ -71590,7 +71801,7 @@ var apiKeyCheck = {
71590
71801
  details: { tier: cfg.tier }
71591
71802
  };
71592
71803
  }
71593
- const rows = ctx.db.select({ placeId: gbpLocations.placeId, selected: gbpLocations.selected }).from(gbpLocations).where(eq103(gbpLocations.projectId, ctx.project.id)).all();
71804
+ const rows = ctx.db.select({ placeId: gbpLocations.placeId, selected: gbpLocations.selected }).from(gbpLocations).where(eq104(gbpLocations.projectId, ctx.project.id)).all();
71594
71805
  const selected = rows.filter((r) => r.selected);
71595
71806
  const locationsWithPlaceId = selected.filter((r) => Boolean(r.placeId)).length;
71596
71807
  const details = {
@@ -71623,7 +71834,7 @@ var PLACES_CHECK_BY_ID = Object.fromEntries(
71623
71834
  );
71624
71835
 
71625
71836
  // ../api-routes/src/doctor/checks/gbp-reviews.ts
71626
- import { and as and88, desc as desc46, eq as eq104, gte as gte29, inArray as inArray47, sql as sql42 } from "drizzle-orm";
71837
+ import { and as and89, desc as desc46, eq as eq105, gte as gte29, inArray as inArray47, sql as sql42 } from "drizzle-orm";
71627
71838
  var REVIEW_EVENTS = /* @__PURE__ */ new Set(["review.negative", "review.rating-dropped"]);
71628
71839
  var V4_ACCESS_REMEDIATION = "Business Profile reviews use the legacy My Business API (mybusiness.googleapis.com), which Google enables per Cloud project only on request; the API Library and `gcloud services enable` cannot turn it on. Open the enable link in the API access approval email as the approved account, or reply to the access-request thread asking Google to enable mybusiness.googleapis.com for your project number, then run `canonry gbp sync`.";
71629
71840
  var reviewsAccessCheck = {
@@ -71651,7 +71862,7 @@ var reviewsAccessCheck = {
71651
71862
  reviewsAccess: gbpLocations.reviewsAccess,
71652
71863
  reviewsAccessReason: gbpLocations.reviewsAccessReason,
71653
71864
  reviewsCheckedAt: gbpLocations.reviewsCheckedAt
71654
- }).from(gbpLocations).where(and88(eq104(gbpLocations.projectId, project.id), eq104(gbpLocations.selected, true))).all();
71865
+ }).from(gbpLocations).where(and89(eq105(gbpLocations.projectId, project.id), eq105(gbpLocations.selected, true))).all();
71655
71866
  if (locations.length === 0) {
71656
71867
  return {
71657
71868
  status: CheckStatuses.skipped,
@@ -71671,7 +71882,7 @@ var reviewsAccessCheck = {
71671
71882
  const places = ctx.getPlacesConfig?.();
71672
71883
  const placesFallback = places !== void 0 && places.tier === "atmosphere" && Boolean(places.apiKey);
71673
71884
  const v4Worked = new Set(
71674
- ctx.db.select({ locationName: gbpReviewRatings.locationName }).from(gbpReviewRatings).where(and88(eq104(gbpReviewRatings.projectId, project.id), eq104(gbpReviewRatings.origin, "gbp"))).all().map((row) => row.locationName)
71885
+ ctx.db.select({ locationName: gbpReviewRatings.locationName }).from(gbpReviewRatings).where(and89(eq105(gbpReviewRatings.projectId, project.id), eq105(gbpReviewRatings.origin, "gbp"))).all().map((row) => row.locationName)
71675
71886
  );
71676
71887
  const coverageOf = (loc) => {
71677
71888
  if (businessProfileReviewsCover(loc.reviewsAccess ?? null, v4Worked.has(loc.locationName))) return "full";
@@ -71705,7 +71916,7 @@ var reviewsAccessCheck = {
71705
71916
  none > 0 ? `${none} have no review source` : null
71706
71917
  ].filter((part) => part !== null);
71707
71918
  const summary3 = `Business Profile reviews are not available${why}. ${parts.join("; ")}.`;
71708
- const subscribed = ctx.db.select({ config: notifications.config, enabled: notifications.enabled }).from(notifications).where(eq104(notifications.projectId, project.id)).all().some((n) => n.enabled && (n.config.events ?? []).some((event) => REVIEW_EVENTS.has(event)));
71919
+ const subscribed = ctx.db.select({ config: notifications.config, enabled: notifications.enabled }).from(notifications).where(eq105(notifications.projectId, project.id)).all().some((n) => n.enabled && (n.config.events ?? []).some((event) => REVIEW_EVENTS.has(event)));
71709
71920
  const placesHint = placesFallback ? "" : " For partial coverage meanwhile, set a Places API key (`GOOGLE_PLACES_API_KEY` or `places.apiKey`) with `places.tier: atmosphere`.";
71710
71921
  if (none > 0 && subscribed) {
71711
71922
  return {
@@ -71744,8 +71955,8 @@ var reviewDeliveryCheck = {
71744
71955
  attempts: reviewWebhookDeliveries.attempts,
71745
71956
  lastTouched,
71746
71957
  payload: reviewWebhookDeliveries.payload
71747
- }).from(reviewWebhookDeliveries).where(and88(
71748
- eq104(reviewWebhookDeliveries.projectId, ctx.project.id),
71958
+ }).from(reviewWebhookDeliveries).where(and89(
71959
+ eq105(reviewWebhookDeliveries.projectId, ctx.project.id),
71749
71960
  inArray47(reviewWebhookDeliveries.state, [ReviewDeliveryStates.Uncertain, ReviewDeliveryStates.Failed]),
71750
71961
  gte29(lastTouched, since)
71751
71962
  )).orderBy(desc46(lastTouched)).all();
@@ -71757,7 +71968,7 @@ var reviewDeliveryCheck = {
71757
71968
  remediation: null
71758
71969
  };
71759
71970
  }
71760
- const destinations = new Map(ctx.db.select({ id: notifications.id, config: notifications.config }).from(notifications).where(eq104(notifications.projectId, ctx.project.id)).all().map((row) => [row.id, redactNotificationUrl(row.config.url).urlDisplay]));
71971
+ const destinations = new Map(ctx.db.select({ id: notifications.id, config: notifications.config }).from(notifications).where(eq105(notifications.projectId, ctx.project.id)).all().map((row) => [row.id, redactNotificationUrl(row.config.url).urlDisplay]));
71761
71972
  const deliveries = rows.map((row) => {
71762
71973
  const review = row.payload.reviews[0];
71763
71974
  return {
@@ -72331,7 +72542,7 @@ var RUNTIME_STATE_CHECKS = [
72331
72542
  ];
72332
72543
 
72333
72544
  // ../api-routes/src/doctor/checks/traffic-source.ts
72334
- import { and as and89, desc as desc47, eq as eq105, gte as gte30, inArray as inArray48, isNotNull as isNotNull9, ne as ne17, sql as sql43 } from "drizzle-orm";
72545
+ import { and as and90, desc as desc47, eq as eq106, gte as gte30, inArray as inArray48, isNotNull as isNotNull9, ne as ne17, sql as sql43 } from "drizzle-orm";
72335
72546
  var RECENT_DATA_WARN_DAYS = 7;
72336
72547
  var RECENT_DATA_FAIL_DAYS = 30;
72337
72548
  function isCloudflareDirectPush(source) {
@@ -72370,8 +72581,8 @@ function skippedNoProject5() {
72370
72581
  function loadProbes(ctx) {
72371
72582
  if (!ctx.project) return [];
72372
72583
  const rows = ctx.db.select().from(trafficSources).where(
72373
- and89(
72374
- eq105(trafficSources.projectId, ctx.project.id),
72584
+ and90(
72585
+ eq106(trafficSources.projectId, ctx.project.id),
72375
72586
  ne17(trafficSources.status, TrafficSourceStatuses.archived)
72376
72587
  )
72377
72588
  ).all();
@@ -72509,8 +72720,8 @@ var recentDataCheck = {
72509
72720
  const failCutoff = new Date(now.getTime() - RECENT_DATA_FAIL_DAYS * 24 * 60 * 6e4).toISOString();
72510
72721
  const recentCrawlers = Number(
72511
72722
  ctx.db.select({ total: sql43`COALESCE(SUM(${crawlerEventsHourly.hits}), 0)` }).from(crawlerEventsHourly).where(
72512
- and89(
72513
- eq105(crawlerEventsHourly.projectId, ctx.project.id),
72723
+ and90(
72724
+ eq106(crawlerEventsHourly.projectId, ctx.project.id),
72514
72725
  inArray48(crawlerEventsHourly.sourceId, activeSourceIds),
72515
72726
  gte30(crawlerEventsHourly.tsHour, warnCutoff)
72516
72727
  )
@@ -72518,8 +72729,8 @@ var recentDataCheck = {
72518
72729
  );
72519
72730
  const recentReferrals = Number(
72520
72731
  ctx.db.select({ total: sql43`COALESCE(SUM(${aiReferralEventsHourly.sessionsOrHits}), 0)` }).from(aiReferralEventsHourly).where(
72521
- and89(
72522
- eq105(aiReferralEventsHourly.projectId, ctx.project.id),
72732
+ and90(
72733
+ eq106(aiReferralEventsHourly.projectId, ctx.project.id),
72523
72734
  inArray48(aiReferralEventsHourly.sourceId, activeSourceIds),
72524
72735
  gte30(aiReferralEventsHourly.tsHour, warnCutoff)
72525
72736
  )
@@ -72527,8 +72738,8 @@ var recentDataCheck = {
72527
72738
  );
72528
72739
  const recentUserFetches = Number(
72529
72740
  ctx.db.select({ total: sql43`COALESCE(SUM(${aiUserFetchEventsHourly.hits}), 0)` }).from(aiUserFetchEventsHourly).where(
72530
- and89(
72531
- eq105(aiUserFetchEventsHourly.projectId, ctx.project.id),
72741
+ and90(
72742
+ eq106(aiUserFetchEventsHourly.projectId, ctx.project.id),
72532
72743
  inArray48(aiUserFetchEventsHourly.sourceId, activeSourceIds),
72533
72744
  gte30(aiUserFetchEventsHourly.tsHour, warnCutoff)
72534
72745
  )
@@ -72553,8 +72764,8 @@ var recentDataCheck = {
72553
72764
  }
72554
72765
  const olderCrawlers = Number(
72555
72766
  ctx.db.select({ total: sql43`COALESCE(SUM(${crawlerEventsHourly.hits}), 0)` }).from(crawlerEventsHourly).where(
72556
- and89(
72557
- eq105(crawlerEventsHourly.projectId, ctx.project.id),
72767
+ and90(
72768
+ eq106(crawlerEventsHourly.projectId, ctx.project.id),
72558
72769
  inArray48(crawlerEventsHourly.sourceId, activeSourceIds),
72559
72770
  gte30(crawlerEventsHourly.tsHour, failCutoff)
72560
72771
  )
@@ -72562,8 +72773,8 @@ var recentDataCheck = {
72562
72773
  );
72563
72774
  const olderReferrals = Number(
72564
72775
  ctx.db.select({ total: sql43`COALESCE(SUM(${aiReferralEventsHourly.sessionsOrHits}), 0)` }).from(aiReferralEventsHourly).where(
72565
- and89(
72566
- eq105(aiReferralEventsHourly.projectId, ctx.project.id),
72776
+ and90(
72777
+ eq106(aiReferralEventsHourly.projectId, ctx.project.id),
72567
72778
  inArray48(aiReferralEventsHourly.sourceId, activeSourceIds),
72568
72779
  gte30(aiReferralEventsHourly.tsHour, failCutoff)
72569
72780
  )
@@ -72571,8 +72782,8 @@ var recentDataCheck = {
72571
72782
  );
72572
72783
  const olderUserFetches = Number(
72573
72784
  ctx.db.select({ total: sql43`COALESCE(SUM(${aiUserFetchEventsHourly.hits}), 0)` }).from(aiUserFetchEventsHourly).where(
72574
- and89(
72575
- eq105(aiUserFetchEventsHourly.projectId, ctx.project.id),
72785
+ and90(
72786
+ eq106(aiUserFetchEventsHourly.projectId, ctx.project.id),
72576
72787
  inArray48(aiUserFetchEventsHourly.sourceId, activeSourceIds),
72577
72788
  gte30(aiUserFetchEventsHourly.tsHour, failCutoff)
72578
72789
  )
@@ -72899,10 +73110,10 @@ var syncLagCheck = {
72899
73110
  }
72900
73111
  };
72901
73112
  function lastQueueStopReason(ctx, sourceId) {
72902
- const row = ctx.db.select({ syncSummary: runs.syncSummary }).from(runs).where(and89(
72903
- eq105(runs.projectId, ctx.project.id),
72904
- eq105(runs.kind, RunKinds["traffic-sync"]),
72905
- eq105(runs.sourceId, sourceId),
73113
+ const row = ctx.db.select({ syncSummary: runs.syncSummary }).from(runs).where(and90(
73114
+ eq106(runs.projectId, ctx.project.id),
73115
+ eq106(runs.kind, RunKinds["traffic-sync"]),
73116
+ eq106(runs.sourceId, sourceId),
72906
73117
  isNotNull9(runs.syncSummary)
72907
73118
  )).orderBy(desc47(runs.createdAt)).limit(1).get();
72908
73119
  return row?.syncSummary?.type === "cloudflare-queue" ? row.syncSummary.stopReason : null;
@@ -73702,7 +73913,7 @@ async function doctorRoutes(app, opts) {
73702
73913
  }
73703
73914
 
73704
73915
  // ../api-routes/src/status.ts
73705
- import { and as and90, desc as desc48, eq as eq106, isNull as isNull15 } from "drizzle-orm";
73916
+ import { and as and91, desc as desc48, eq as eq107, isNull as isNull15 } from "drizzle-orm";
73706
73917
  function ageDays(iso2) {
73707
73918
  if (!iso2) return null;
73708
73919
  const ms = Date.now() - Date.parse(iso2);
@@ -73752,7 +73963,7 @@ function makeRow(integration, label, runKind, state, staleAfterDays, dataThrough
73752
73963
  };
73753
73964
  }
73754
73965
  function latestDate2(db, table, projectId) {
73755
- const row = db.select({ date: table.date }).from(table).where(eq106(table.projectId, projectId)).orderBy(desc48(table.date)).limit(1).get();
73966
+ const row = db.select({ date: table.date }).from(table).where(eq107(table.projectId, projectId)).orderBy(desc48(table.date)).limit(1).get();
73756
73967
  return row?.date ?? null;
73757
73968
  }
73758
73969
  function projectRows(opts, project) {
@@ -73767,7 +73978,7 @@ function projectRows(opts, project) {
73767
73978
  const rows = [];
73768
73979
  const gsc = gscConnection(ctx, info);
73769
73980
  if (connected(gsc)) {
73770
- const watermarkRow = opts.db.select({ dataThroughDate: gscDataWatermarks.dataThroughDate }).from(gscDataWatermarks).where(eq106(gscDataWatermarks.projectId, project.id)).limit(1).get();
73981
+ const watermarkRow = opts.db.select({ dataThroughDate: gscDataWatermarks.dataThroughDate }).from(gscDataWatermarks).where(eq107(gscDataWatermarks.projectId, project.id)).limit(1).get();
73771
73982
  const state = readSyncRunState(opts.db, project.id, RunKinds["gsc-sync"], { since: gsc.since });
73772
73983
  const dataThrough = watermarkRow?.dataThroughDate ?? latestDate2(opts.db, gscDailyTotals, project.id);
73773
73984
  rows.push(makeRow("gsc", "Search Console", RunKinds["gsc-sync"], state, SYNC_IDLE_DAYS, dataThrough, null, ["gsc.sync.recent-failures"], GSC_DATA_AGING_DAYS));
@@ -73783,12 +73994,12 @@ function projectRows(opts, project) {
73783
73994
  rows.push(makeRow("bing", "Bing Webmaster Tools", RunKinds["bing-inspect-sitemap"], readSyncRunState(opts.db, project.id, RunKinds["bing-inspect-sitemap"]), null));
73784
73995
  rows.push(makeRow("bing-search", "Bing search data", RunKinds["bing-sync"], readSyncRunState(opts.db, project.id, RunKinds["bing-sync"]), SYNC_IDLE_DAYS, latestDate2(opts.db, bingTrafficDaily, project.id), null, ["bing.sync.recent-failures"], BING_DATA_AGING_DAYS));
73785
73996
  }
73786
- const ads = opts.db.select({ id: adsConnections.id, lastSyncedAt: adsConnections.lastSyncedAt }).from(adsConnections).where(eq106(adsConnections.projectId, project.id)).limit(1).get();
73997
+ const ads = opts.db.select({ id: adsConnections.id, lastSyncedAt: adsConnections.lastSyncedAt }).from(adsConnections).where(eq107(adsConnections.projectId, project.id)).limit(1).get();
73787
73998
  if (ads) rows.push(makeRow("ads", "OpenAI Ads", RunKinds["ads-sync"], readSyncRunState(opts.db, project.id, RunKinds["ads-sync"]), ADS_RECENT_SYNC_WARN_DAYS, null, null, ["ads.data.recent-sync"], ADS_RECENT_SYNC_WARN_DAYS, ads.lastSyncedAt));
73788
73999
  const marketing = opts.getGoogleMarketingDoctorInput?.(ctx);
73789
74000
  if (marketing?.googleAds) rows.push(makeRow("google-ads", "Google Ads", RunKinds["google-ads-sync"], readSyncRunState(opts.db, project.id, RunKinds["google-ads-sync"]), FRESH_SNAPSHOT_WARN_DAYS, null, null, ["google-ads.sync.recent-failures"], FRESH_SNAPSHOT_WARN_DAYS, marketing.googleAds.latestSnapshotAt));
73790
74001
  if (marketing?.gtm) rows.push(makeRow("gtm", "Google Tag Manager", RunKinds["gtm-sync"], readSyncRunState(opts.db, project.id, RunKinds["gtm-sync"]), FRESH_SNAPSHOT_WARN_DAYS, null, null, ["gtm.sync.recent-failures"], FRESH_SNAPSHOT_WARN_DAYS, marketing.gtm.latestSnapshotAt));
73791
- const sources = opts.db.select().from(trafficSources).where(and90(eq106(trafficSources.projectId, project.id), isNull15(trafficSources.archivedAt))).all();
74002
+ const sources = opts.db.select().from(trafficSources).where(and91(eq107(trafficSources.projectId, project.id), isNull15(trafficSources.archivedAt))).all();
73792
74003
  for (const source of sources) {
73793
74004
  const row = makeRow("traffic", source.displayName, RunKinds["traffic-sync"], readSyncRunState(opts.db, project.id, RunKinds["traffic-sync"], { sourceId: source.id }), null, null, source.id, ["traffic.source.sync-lag", "traffic.source.recent-data"]);
73794
74005
  const lag = source.lastSyncedAt ? Date.now() - Date.parse(source.lastSyncedAt) : Number.POSITIVE_INFINITY;
@@ -73800,7 +74011,7 @@ function projectRows(opts, project) {
73800
74011
  }
73801
74012
  function projectStatus(opts, project) {
73802
74013
  const integrations = projectRows(opts, project);
73803
- const healthRow = opts.db.select().from(doctorHealthState).where(eq106(doctorHealthState.projectId, project.id)).limit(1).get();
74014
+ const healthRow = opts.db.select().from(doctorHealthState).where(eq107(doctorHealthState.projectId, project.id)).limit(1).get();
73804
74015
  const health = healthRow ? { status: healthRow.status, code: healthRow.code, summary: healthRow.summary, checkedAt: healthRow.checkedAt, notifiedAt: healthRow.notifiedAt } : null;
73805
74016
  const rank = { ok: 0, "never-synced": 1, warning: 2, stale: 3, failing: 4 };
73806
74017
  const healthState = health?.status === "fail" ? "failing" : health?.status === "warn" ? "warning" : "ok";
@@ -73820,7 +74031,7 @@ async function statusRoutes(app, opts) {
73820
74031
  if (request.query.problems === "true") result.projects = result.projects.map((item2) => ({ ...item2, integrations: item2.integrations.filter((row) => row.state !== "ok") }));
73821
74032
  return instanceStatusDtoSchema.parse(result);
73822
74033
  }
73823
- const projectsRows = (scopedId ? opts.db.select().from(projects).where(eq106(projects.id, scopedId)) : opts.db.select().from(projects)).all();
74034
+ const projectsRows = (scopedId ? opts.db.select().from(projects).where(eq107(projects.id, scopedId)) : opts.db.select().from(projects)).all();
73824
74035
  const output = { generatedAt: (/* @__PURE__ */ new Date()).toISOString(), ...opts.getMcpHealth ? { mcp: opts.getMcpHealth() } : {}, summary: { projects: projectsRows.length, ok: 0, failing: 0, warning: 0, stale: 0, neverSynced: 0 }, projects: projectsRows.map((project) => projectStatus(opts, project)) };
73825
74036
  for (const project of output.projects) {
73826
74037
  const key = project.state === "never-synced" ? "neverSynced" : project.state;
@@ -73833,7 +74044,7 @@ async function statusRoutes(app, opts) {
73833
74044
 
73834
74045
  // ../api-routes/src/discovery/routes.ts
73835
74046
  import crypto57 from "crypto";
73836
- import { and as and91, desc as desc49, eq as eq107, gte as gte31, inArray as inArray49, isNull as isNull16, or as or20 } from "drizzle-orm";
74047
+ import { and as and92, desc as desc49, eq as eq108, gte as gte31, inArray as inArray49, isNull as isNull16, or as or20 } from "drizzle-orm";
73837
74048
  var MAX_INFLIGHT_DISCOVERY_AGE_MS = 2 * 60 * 60 * 1e3;
73838
74049
  async function discoveryRoutes(app, opts) {
73839
74050
  app.post("/projects/:name/discover/run", async (request, reply) => {
@@ -73867,13 +74078,13 @@ async function discoveryRoutes(app, opts) {
73867
74078
  const now = (/* @__PURE__ */ new Date()).toISOString();
73868
74079
  const ageFloorIso = new Date(Date.now() - MAX_INFLIGHT_DISCOVERY_AGE_MS).toISOString();
73869
74080
  const decision = app.db.transaction((tx) => {
73870
- const existing = tx.select({ id: discoverySessions.id, runId: discoverySessions.runId }).from(discoverySessions).where(and91(
73871
- eq107(discoverySessions.projectId, project.id),
73872
- eq107(discoverySessions.icpDescription, icpDescription),
74081
+ const existing = tx.select({ id: discoverySessions.id, runId: discoverySessions.runId }).from(discoverySessions).where(and92(
74082
+ eq108(discoverySessions.projectId, project.id),
74083
+ eq108(discoverySessions.icpDescription, icpDescription),
73873
74084
  // Buyer is part of session identity: it changes the seed prompt's
73874
74085
  // semantics, so a request with a different (or no) buyer must start
73875
74086
  // its own session, never adopt another buyer's probes.
73876
- parsed.data.buyerDescription == null ? isNull16(discoverySessions.buyerDescription) : eq107(discoverySessions.buyerDescription, parsed.data.buyerDescription),
74087
+ parsed.data.buyerDescription == null ? isNull16(discoverySessions.buyerDescription) : eq108(discoverySessions.buyerDescription, parsed.data.buyerDescription),
73877
74088
  // Locations are identity too: a different service-area subset seeds
73878
74089
  // and probes a different geo, so it must never reuse another geo's
73879
74090
  // session. resolveLocations is deterministic (project-config order),
@@ -73883,11 +74094,11 @@ async function discoveryRoutes(app, opts) {
73883
74094
  // locations a NULL row's subset is unknowable, so it conservatively
73884
74095
  // never reuses — a one-time, bounded (2h window) non-reuse after
73885
74096
  // upgrade, never a wrong reuse.
73886
- locations.length === 0 ? or20(isNull16(discoverySessions.locations), eq107(discoverySessions.locations, locations)) : eq107(discoverySessions.locations, locations),
74097
+ locations.length === 0 ? or20(isNull16(discoverySessions.locations), eq108(discoverySessions.locations, locations)) : eq108(discoverySessions.locations, locations),
73887
74098
  // Seed provider set is identity: a different phrasing distribution
73888
74099
  // must never reuse another set's session. Null (= Gemini-only
73889
74100
  // default, including explicit ['gemini']) matches legacy rows.
73890
- seedProviders == null ? isNull16(discoverySessions.seedProviders) : eq107(discoverySessions.seedProviders, seedProviders),
74101
+ seedProviders == null ? isNull16(discoverySessions.seedProviders) : eq108(discoverySessions.seedProviders, seedProviders),
73891
74102
  inArray49(discoverySessions.status, [
73892
74103
  DiscoverySessionStatuses.queued,
73893
74104
  DiscoverySessionStatuses.seeding,
@@ -73963,7 +74174,7 @@ async function discoveryRoutes(app, opts) {
73963
74174
  const project = resolveProject(app.db, request.params.name);
73964
74175
  const parsedLimit = parseInt(request.query.limit ?? "", 10);
73965
74176
  const limit = Number.isNaN(parsedLimit) || parsedLimit <= 0 ? 50 : parsedLimit;
73966
- const rows = app.db.select().from(discoverySessions).where(eq107(discoverySessions.projectId, project.id)).orderBy(desc49(discoverySessions.createdAt)).limit(limit).all();
74177
+ const rows = app.db.select().from(discoverySessions).where(eq108(discoverySessions.projectId, project.id)).orderBy(desc49(discoverySessions.createdAt)).limit(limit).all();
73967
74178
  return reply.send(rows.map(serializeSession));
73968
74179
  }
73969
74180
  );
@@ -73971,11 +74182,11 @@ async function discoveryRoutes(app, opts) {
73971
74182
  "/projects/:name/discover/sessions/:id",
73972
74183
  async (request, reply) => {
73973
74184
  const project = resolveProject(app.db, request.params.name);
73974
- const session = app.db.select().from(discoverySessions).where(eq107(discoverySessions.id, request.params.id)).get();
74185
+ const session = app.db.select().from(discoverySessions).where(eq108(discoverySessions.id, request.params.id)).get();
73975
74186
  if (!session || session.projectId !== project.id) {
73976
74187
  throw notFound("Discovery session", request.params.id);
73977
74188
  }
73978
- const probeRows = app.db.select().from(discoveryProbes).where(eq107(discoveryProbes.sessionId, session.id)).all();
74189
+ const probeRows = app.db.select().from(discoveryProbes).where(eq108(discoveryProbes.sessionId, session.id)).all();
73979
74190
  const detail = {
73980
74191
  ...serializeSession(session),
73981
74192
  probes: probeRows.map(serializeProbe)
@@ -73987,7 +74198,7 @@ async function discoveryRoutes(app, opts) {
73987
74198
  "/projects/:name/discover/sessions/:id/harvest",
73988
74199
  async (request, reply) => {
73989
74200
  const project = resolveProject(app.db, request.params.name);
73990
- const session = app.db.select().from(discoverySessions).where(eq107(discoverySessions.id, request.params.id)).get();
74201
+ const session = app.db.select().from(discoverySessions).where(eq108(discoverySessions.id, request.params.id)).get();
73991
74202
  if (!session || session.projectId !== project.id) {
73992
74203
  throw notFound("Discovery session", request.params.id);
73993
74204
  }
@@ -73995,7 +74206,7 @@ async function discoveryRoutes(app, opts) {
73995
74206
  const minProbeHits = Number.isNaN(parsedFloor) || parsedFloor < 1 ? 1 : parsedFloor;
73996
74207
  const applyAnchor = request.query.anchor !== "false";
73997
74208
  const provider = session.seedProvider ?? "gemini";
73998
- const probeRows = app.db.select().from(discoveryProbes).where(eq107(discoveryProbes.sessionId, session.id)).all();
74209
+ const probeRows = app.db.select().from(discoveryProbes).where(eq108(discoveryProbes.sessionId, session.id)).all();
73999
74210
  const extract = opts.harvestSearchQueries;
74000
74211
  const probesWithQueries = probeRows.map((row) => {
74001
74212
  if (!extract || !row.rawResponse) return { searchQueries: [] };
@@ -74006,7 +74217,7 @@ async function discoveryRoutes(app, opts) {
74006
74217
  return { searchQueries: [] };
74007
74218
  }
74008
74219
  });
74009
- const trackedQueries = app.db.select({ query: queries.query }).from(queries).where(eq107(queries.projectId, project.id)).all().map((r) => r.query);
74220
+ const trackedQueries = app.db.select({ query: queries.query }).from(queries).where(eq108(queries.projectId, project.id)).all().map((r) => r.query);
74010
74221
  const anchorTerms = buildHarvestAnchorTerms(
74011
74222
  [session.icpDescription ?? "", ...trackedQueries],
74012
74223
  effectiveDomains(project)
@@ -74053,11 +74264,11 @@ async function discoveryRoutes(app, opts) {
74053
74264
  "/projects/:name/discover/sessions/:id/promote",
74054
74265
  async (request, reply) => {
74055
74266
  const project = resolveProject(app.db, request.params.name);
74056
- const session = app.db.select().from(discoverySessions).where(eq107(discoverySessions.id, request.params.id)).get();
74267
+ const session = app.db.select().from(discoverySessions).where(eq108(discoverySessions.id, request.params.id)).get();
74057
74268
  if (!session || session.projectId !== project.id) {
74058
74269
  throw notFound("Discovery session", request.params.id);
74059
74270
  }
74060
- const probeRows = app.db.select().from(discoveryProbes).where(eq107(discoveryProbes.sessionId, session.id)).all();
74271
+ const probeRows = app.db.select().from(discoveryProbes).where(eq108(discoveryProbes.sessionId, session.id)).all();
74061
74272
  const cited = /* @__PURE__ */ new Set();
74062
74273
  const aspirational = /* @__PURE__ */ new Set();
74063
74274
  const wasted = /* @__PURE__ */ new Set();
@@ -74092,7 +74303,7 @@ async function discoveryRoutes(app, opts) {
74092
74303
  );
74093
74304
  app.post("/projects/:name/discover/sessions/:id/promote", async (request, reply) => {
74094
74305
  const project = resolveProject(app.db, request.params.name);
74095
- const session = app.db.select().from(discoverySessions).where(eq107(discoverySessions.id, request.params.id)).get();
74306
+ const session = app.db.select().from(discoverySessions).where(eq108(discoverySessions.id, request.params.id)).get();
74096
74307
  if (!session || session.projectId !== project.id) {
74097
74308
  throw notFound("Discovery session", request.params.id);
74098
74309
  }
@@ -74115,7 +74326,7 @@ async function discoveryRoutes(app, opts) {
74115
74326
  const bucketSet = new Set(buckets);
74116
74327
  const includeCompetitors = parsed.data.includeCompetitors ?? true;
74117
74328
  const competitorTypes = parsed.data.competitorTypes ?? DEFAULT_DISCOVERY_PROMOTE_COMPETITOR_TYPES;
74118
- const probeRows = app.db.select().from(discoveryProbes).where(eq107(discoveryProbes.sessionId, session.id)).all();
74329
+ const probeRows = app.db.select().from(discoveryProbes).where(eq108(discoveryProbes.sessionId, session.id)).all();
74119
74330
  const candidateQueries = /* @__PURE__ */ new Set();
74120
74331
  for (const probe of probeRows) {
74121
74332
  if (!probe.bucket) continue;
@@ -74123,7 +74334,7 @@ async function discoveryRoutes(app, opts) {
74123
74334
  if (bucket.success && bucketSet.has(bucket.data)) candidateQueries.add(probe.query);
74124
74335
  }
74125
74336
  const existingQueries = new Set(
74126
- app.db.select({ query: queries.query }).from(queries).where(eq107(queries.projectId, project.id)).all().map((r) => r.query.toLowerCase())
74337
+ app.db.select({ query: queries.query }).from(queries).where(eq108(queries.projectId, project.id)).all().map((r) => r.query.toLowerCase())
74127
74338
  );
74128
74339
  const promotedQueries = [];
74129
74340
  const skippedQueries = [];
@@ -74337,7 +74548,7 @@ function planCompetitorPromotion(input) {
74337
74548
 
74338
74549
  // ../api-routes/src/discovery/orchestrate.ts
74339
74550
  import crypto58 from "crypto";
74340
- import { eq as eq108 } from "drizzle-orm";
74551
+ import { eq as eq109 } from "drizzle-orm";
74341
74552
  var DEFAULT_MAX_PROBES = 100;
74342
74553
  var ABSOLUTE_MAX_PROBES = 500;
74343
74554
  function classifyProbeBucket(input) {
@@ -74423,7 +74634,7 @@ async function executeDiscovery(opts) {
74423
74634
  status: DiscoverySessionStatuses.seeding,
74424
74635
  dedupThreshold,
74425
74636
  startedAt
74426
- }).where(eq108(discoverySessions.id, opts.sessionId)).run();
74637
+ }).where(eq109(discoverySessions.id, opts.sessionId)).run();
74427
74638
  const seedResult = await opts.deps.seed({
74428
74639
  project: opts.project,
74429
74640
  icpDescription: opts.icpDescription,
@@ -74484,7 +74695,7 @@ async function executeDiscovery(opts) {
74484
74695
  dedupBandPairFraction: dedupStats.bandPairFraction,
74485
74696
  dedupPairsTotal: dedupStats.pairsTotal,
74486
74697
  warning
74487
- }).where(eq108(discoverySessions.id, opts.sessionId)).run();
74698
+ }).where(eq109(discoverySessions.id, opts.sessionId)).run();
74488
74699
  const probeLocation = opts.locations?.[0];
74489
74700
  const probeResults = await mapWithConcurrency(
74490
74701
  probedCanonicals,
@@ -74538,7 +74749,7 @@ async function executeDiscovery(opts) {
74538
74749
  wastedCount: buckets["wasted-surface"],
74539
74750
  competitorMap,
74540
74751
  finishedAt: (/* @__PURE__ */ new Date()).toISOString()
74541
- }).where(eq108(discoverySessions.id, opts.sessionId)).run();
74752
+ }).where(eq109(discoverySessions.id, opts.sessionId)).run();
74542
74753
  upsertDomainClassifications(opts.db, opts.project.id, opts.sessionId, competitorMap);
74543
74754
  return {
74544
74755
  buckets,
@@ -74578,7 +74789,7 @@ function markSessionFailed(db, sessionId, error) {
74578
74789
  status: DiscoverySessionStatuses.failed,
74579
74790
  error,
74580
74791
  finishedAt: (/* @__PURE__ */ new Date()).toISOString()
74581
- }).where(eq108(discoverySessions.id, sessionId)).run();
74792
+ }).where(eq109(discoverySessions.id, sessionId)).run();
74582
74793
  }
74583
74794
  function dedupeStrings(input) {
74584
74795
  const seen2 = /* @__PURE__ */ new Set();
@@ -74596,12 +74807,12 @@ function dedupeStrings(input) {
74596
74807
 
74597
74808
  // ../api-routes/src/technical-aeo.ts
74598
74809
  import crypto59 from "crypto";
74599
- import { and as and93, asc as asc27, count as count8, desc as desc50, eq as eq110, gte as gte32, inArray as inArray50, isNotNull as isNotNull10, isNull as isNull17, lt as lt18, or as or21, sql as sql45 } from "drizzle-orm";
74810
+ import { and as and94, asc as asc27, count as count8, desc as desc50, eq as eq111, gte as gte32, inArray as inArray50, isNotNull as isNotNull10, isNull as isNull17, lt as lt19, or as or21, sql as sql45 } from "drizzle-orm";
74600
74811
  import { alias } from "drizzle-orm/sqlite-core";
74601
74812
 
74602
74813
  // ../api-routes/src/technical-aeo-issues.ts
74603
74814
  import { createHash as createHash10 } from "crypto";
74604
- import { and as and92, asc as asc26, eq as eq109, gt as gt10, sql as sql44 } from "drizzle-orm";
74815
+ import { and as and93, asc as asc26, eq as eq110, gt as gt10, sql as sql44 } from "drizzle-orm";
74605
74816
  var ADVISORY_CODES = /* @__PURE__ */ new Set([
74606
74817
  "entity-consistency.title.short",
74607
74818
  "site.duplicate-title",
@@ -74746,10 +74957,10 @@ function readSiteAuditIssues(input) {
74746
74957
  }
74747
74958
  }
74748
74959
  if (snapshot.detailsAvailable && snapshot.attemptId) {
74749
- const scope = and92(
74750
- eq109(siteCrawlPages.projectId, project.id),
74751
- eq109(siteCrawlPages.runId, snapshot.runId),
74752
- eq109(siteCrawlPages.attemptId, snapshot.attemptId)
74960
+ const scope = and93(
74961
+ eq110(siteCrawlPages.projectId, project.id),
74962
+ eq110(siteCrawlPages.runId, snapshot.runId),
74963
+ eq110(siteCrawlPages.attemptId, snapshot.attemptId)
74753
74964
  );
74754
74965
  const size = db.select({ bytes: sql44`coalesce(sum(length(cast(${siteCrawlPages.auditFields} as blob)) + length(cast(${siteCrawlPages.url} as blob))), 0)` }).from(siteCrawlPages).where(scope).get()?.bytes ?? 0;
74755
74966
  if (size + crossBytes > MAX_EVIDENCE_BYTES) {
@@ -74762,7 +74973,7 @@ function readSiteAuditIssues(input) {
74762
74973
  url: siteCrawlPages.url,
74763
74974
  auditScore: siteCrawlPages.auditScore,
74764
74975
  auditFields: siteCrawlPages.auditFields
74765
- }).from(siteCrawlPages).where(and92(scope, after === void 0 ? void 0 : gt10(siteCrawlPages.nodeKey, after))).orderBy(asc26(siteCrawlPages.nodeKey)).limit(200).all();
74976
+ }).from(siteCrawlPages).where(and93(scope, after === void 0 ? void 0 : gt10(siteCrawlPages.nodeKey, after))).orderBy(asc26(siteCrawlPages.nodeKey)).limit(200).all();
74766
74977
  for (const page2 of pages) {
74767
74978
  evidenceHash.update(JSON.stringify(page2));
74768
74979
  const fields = record(page2.auditFields);
@@ -74899,9 +75110,9 @@ var FETCHED_SITE_CRAWL_STATES = /* @__PURE__ */ new Set([
74899
75110
  var SURFACEABLE_STATUSES = [RunStatuses.completed, RunStatuses.partial];
74900
75111
  function surfaceableSiteAuditFilters(projectId) {
74901
75112
  return [
74902
- eq110(siteAuditSnapshots.projectId, projectId),
74903
- eq110(runs.projectId, projectId),
74904
- eq110(runs.kind, RunKinds["site-audit"]),
75113
+ eq111(siteAuditSnapshots.projectId, projectId),
75114
+ eq111(runs.projectId, projectId),
75115
+ eq111(runs.kind, RunKinds["site-audit"]),
74905
75116
  inArray50(runs.status, SURFACEABLE_STATUSES),
74906
75117
  notProbeRun()
74907
75118
  ];
@@ -75142,8 +75353,8 @@ function parseLinkKind(value) {
75142
75353
  return allowed.data;
75143
75354
  }
75144
75355
  function linkKindFilter(linkKind) {
75145
- if (linkKind === SiteHealthLinkKinds.content) return eq110(siteCrawlEdges.isTemplate, false);
75146
- if (linkKind === SiteHealthLinkKinds.template) return eq110(siteCrawlEdges.isTemplate, true);
75356
+ if (linkKind === SiteHealthLinkKinds.content) return eq111(siteCrawlEdges.isTemplate, false);
75357
+ if (linkKind === SiteHealthLinkKinds.template) return eq111(siteCrawlEdges.isTemplate, true);
75147
75358
  return void 0;
75148
75359
  }
75149
75360
  var graphSourceNode = alias(siteCrawlGraphNodes, "site_crawl_graph_source_node");
@@ -75247,9 +75458,9 @@ function changedFields(before, after, fields) {
75247
75458
  }
75248
75459
  async function technicalAeoRoutes(app, opts) {
75249
75460
  const visibleCrawlFilters = (projectId) => [
75250
- eq110(siteCrawlSnapshots.projectId, projectId),
75251
- eq110(runs.projectId, projectId),
75252
- eq110(runs.kind, RunKinds["site-audit"]),
75461
+ eq111(siteCrawlSnapshots.projectId, projectId),
75462
+ eq111(runs.projectId, projectId),
75463
+ eq111(runs.kind, RunKinds["site-audit"]),
75253
75464
  inArray50(runs.status, SURFACEABLE_STATUSES),
75254
75465
  notProbeRun()
75255
75466
  ];
@@ -75257,10 +75468,10 @@ async function technicalAeoRoutes(app, opts) {
75257
75468
  if (!requestedDate) return void 0;
75258
75469
  const date = sql45`substr(${siteCrawlSnapshots.createdAt}, 1, 10)`;
75259
75470
  const filters = visibleCrawlFilters(projectId);
75260
- const allDates = and93(...filters);
75261
- const matchingDates = and93(...filters, eq110(sql45`substr(${siteCrawlSnapshots.createdAt}, 6, 5)`, requestedDate.slice(5)));
75262
- const readDates = (where) => app.db.select({ date }).from(siteCrawlSnapshots).innerJoin(runs, eq110(siteCrawlSnapshots.runId, runs.id)).where(where).groupBy(date).orderBy(desc50(date)).limit(10).all().map((row) => row.date);
75263
- const countDates = (where) => app.db.select({ value: sql45`count(distinct ${date})` }).from(siteCrawlSnapshots).innerJoin(runs, eq110(siteCrawlSnapshots.runId, runs.id)).where(where).get()?.value ?? 0;
75471
+ const allDates = and94(...filters);
75472
+ const matchingDates = and94(...filters, eq111(sql45`substr(${siteCrawlSnapshots.createdAt}, 6, 5)`, requestedDate.slice(5)));
75473
+ const readDates = (where) => app.db.select({ date }).from(siteCrawlSnapshots).innerJoin(runs, eq111(siteCrawlSnapshots.runId, runs.id)).where(where).groupBy(date).orderBy(desc50(date)).limit(10).all().map((row) => row.date);
75474
+ const countDates = (where) => app.db.select({ value: sql45`count(distinct ${date})` }).from(siteCrawlSnapshots).innerJoin(runs, eq111(siteCrawlSnapshots.runId, runs.id)).where(where).get()?.value ?? 0;
75264
75475
  return {
75265
75476
  recentDates: readDates(allDates),
75266
75477
  totalDates: countDates(allDates),
@@ -75270,15 +75481,15 @@ async function technicalAeoRoutes(app, opts) {
75270
75481
  };
75271
75482
  const resolveCrawl = (projectId, runId, requestedDate, scope = {}) => {
75272
75483
  const filters = visibleCrawlFilters(projectId);
75273
- if (scope.recordedBefore) filters.push(lt18(siteCrawlSnapshots.createdAt, scope.recordedBefore));
75484
+ if (scope.recordedBefore) filters.push(lt19(siteCrawlSnapshots.createdAt, scope.recordedBefore));
75274
75485
  const newestIfComplete = sql45`case when ${siteCrawlSnapshots.complete} then ${siteCrawlSnapshots.createdAt} end`;
75275
75486
  if (requestedDate) {
75276
75487
  const nextDate2 = /* @__PURE__ */ new Date(`${requestedDate}T00:00:00.000Z`);
75277
75488
  nextDate2.setUTCDate(nextDate2.getUTCDate() + 1);
75278
- filters.push(gte32(siteCrawlSnapshots.createdAt, requestedDate), lt18(siteCrawlSnapshots.createdAt, nextDate2.toISOString()));
75489
+ filters.push(gte32(siteCrawlSnapshots.createdAt, requestedDate), lt19(siteCrawlSnapshots.createdAt, nextDate2.toISOString()));
75279
75490
  }
75280
75491
  const selectionFilters = scope.scoredOnly ? [...filters, sql45`exists (select 1 from ${siteAuditSnapshots} where ${siteAuditSnapshots.projectId} = ${siteCrawlSnapshots.projectId} and ${siteAuditSnapshots.runId} = ${siteCrawlSnapshots.runId})`] : filters;
75281
- const selected = app.db.select({ snapshot: siteCrawlSnapshots, runStatus: runs.status }).from(siteCrawlSnapshots).innerJoin(runs, eq110(siteCrawlSnapshots.runId, runs.id)).where(and93(...selectionFilters, runId ? eq110(siteCrawlSnapshots.runId, runId) : void 0)).orderBy(
75492
+ const selected = app.db.select({ snapshot: siteCrawlSnapshots, runStatus: runs.status }).from(siteCrawlSnapshots).innerJoin(runs, eq111(siteCrawlSnapshots.runId, runs.id)).where(and94(...selectionFilters, runId ? eq111(siteCrawlSnapshots.runId, runId) : void 0)).orderBy(
75282
75493
  desc50(sql45`substr(${siteCrawlSnapshots.createdAt}, 1, 10)`),
75283
75494
  desc50(siteCrawlSnapshots.complete),
75284
75495
  desc50(newestIfComplete),
@@ -75290,18 +75501,18 @@ async function technicalAeoRoutes(app, opts) {
75290
75501
  const date = selected.snapshot.createdAt.slice(0, 10);
75291
75502
  const nextDate = /* @__PURE__ */ new Date(`${date}T00:00:00.000Z`);
75292
75503
  nextDate.setUTCDate(nextDate.getUTCDate() + 1);
75293
- const sameDate = and93(
75504
+ const sameDate = and94(
75294
75505
  ...filters,
75295
75506
  gte32(siteCrawlSnapshots.createdAt, date),
75296
- lt18(siteCrawlSnapshots.createdAt, nextDate.toISOString())
75507
+ lt19(siteCrawlSnapshots.createdAt, nextDate.toISOString())
75297
75508
  );
75298
- const sameDateRunCount = app.db.select({ value: count8() }).from(siteCrawlSnapshots).innerJoin(runs, eq110(siteCrawlSnapshots.runId, runs.id)).where(sameDate).get()?.value ?? 0;
75509
+ const sameDateRunCount = app.db.select({ value: count8() }).from(siteCrawlSnapshots).innerJoin(runs, eq111(siteCrawlSnapshots.runId, runs.id)).where(sameDate).get()?.value ?? 0;
75299
75510
  const candidates = app.db.select({
75300
75511
  runId: siteCrawlSnapshots.runId,
75301
75512
  createdAt: siteCrawlSnapshots.createdAt,
75302
75513
  complete: siteCrawlSnapshots.complete,
75303
75514
  pages: siteCrawlSnapshots.pagesFetched
75304
- }).from(siteCrawlSnapshots).innerJoin(runs, eq110(siteCrawlSnapshots.runId, runs.id)).where(sameDate).orderBy(
75515
+ }).from(siteCrawlSnapshots).innerJoin(runs, eq111(siteCrawlSnapshots.runId, runs.id)).where(sameDate).orderBy(
75305
75516
  desc50(siteCrawlSnapshots.complete),
75306
75517
  desc50(newestIfComplete),
75307
75518
  desc50(siteCrawlSnapshots.pagesFetched),
@@ -75321,11 +75532,11 @@ async function technicalAeoRoutes(app, opts) {
75321
75532
  const resolveAudit = (projectId, runId, recordedBefore) => {
75322
75533
  const filters = [
75323
75534
  ...surfaceableSiteAuditFilters(projectId),
75324
- ...recordedBefore ? [lt18(siteAuditSnapshots.createdAt, recordedBefore)] : []
75535
+ ...recordedBefore ? [lt19(siteAuditSnapshots.createdAt, recordedBefore)] : []
75325
75536
  ];
75326
75537
  const complete = sql45`case when ${runs.status} = ${RunStatuses.completed} then 1 else 0 end`;
75327
75538
  const newestIfComplete = sql45`case when ${runs.status} = ${RunStatuses.completed} then ${siteAuditSnapshots.createdAt} end`;
75328
- const selectAudit = (pinnedRunId) => app.db.select({ snap: siteAuditSnapshots, runStatus: runs.status }).from(siteAuditSnapshots).innerJoin(runs, eq110(siteAuditSnapshots.runId, runs.id)).where(and93(...filters, pinnedRunId ? eq110(siteAuditSnapshots.runId, pinnedRunId) : void 0)).orderBy(
75539
+ const selectAudit = (pinnedRunId) => app.db.select({ snap: siteAuditSnapshots, runStatus: runs.status }).from(siteAuditSnapshots).innerJoin(runs, eq111(siteAuditSnapshots.runId, runs.id)).where(and94(...filters, pinnedRunId ? eq111(siteAuditSnapshots.runId, pinnedRunId) : void 0)).orderBy(
75329
75540
  desc50(sql45`substr(${siteAuditSnapshots.createdAt}, 1, 10)`),
75330
75541
  desc50(complete),
75331
75542
  desc50(newestIfComplete),
@@ -75347,18 +75558,18 @@ async function technicalAeoRoutes(app, opts) {
75347
75558
  const date = selected.snap.createdAt.slice(0, 10);
75348
75559
  const nextDate = /* @__PURE__ */ new Date(`${date}T00:00:00.000Z`);
75349
75560
  nextDate.setUTCDate(nextDate.getUTCDate() + 1);
75350
- const sameDate = and93(
75561
+ const sameDate = and94(
75351
75562
  ...filters,
75352
75563
  gte32(siteAuditSnapshots.createdAt, date),
75353
- lt18(siteAuditSnapshots.createdAt, nextDate.toISOString())
75564
+ lt19(siteAuditSnapshots.createdAt, nextDate.toISOString())
75354
75565
  );
75355
- const sameDateRunCount = app.db.select({ value: count8() }).from(siteAuditSnapshots).innerJoin(runs, eq110(siteAuditSnapshots.runId, runs.id)).where(sameDate).get()?.value ?? 0;
75566
+ const sameDateRunCount = app.db.select({ value: count8() }).from(siteAuditSnapshots).innerJoin(runs, eq111(siteAuditSnapshots.runId, runs.id)).where(sameDate).get()?.value ?? 0;
75356
75567
  const candidates = app.db.select({
75357
75568
  runId: siteAuditSnapshots.runId,
75358
75569
  createdAt: siteAuditSnapshots.createdAt,
75359
75570
  complete,
75360
75571
  pages: siteAuditSnapshots.pagesAudited
75361
- }).from(siteAuditSnapshots).innerJoin(runs, eq110(siteAuditSnapshots.runId, runs.id)).where(sameDate).orderBy(
75572
+ }).from(siteAuditSnapshots).innerJoin(runs, eq111(siteAuditSnapshots.runId, runs.id)).where(sameDate).orderBy(
75362
75573
  desc50(complete),
75363
75574
  desc50(newestIfComplete),
75364
75575
  desc50(siteAuditSnapshots.pagesAudited),
@@ -75425,19 +75636,19 @@ async function technicalAeoRoutes(app, opts) {
75425
75636
  };
75426
75637
  const inventorySummaryFor = (scope) => {
75427
75638
  if (!scope) return null;
75428
- const inScope = and93(
75429
- eq110(siteCrawlPages.projectId, scope.projectId),
75430
- eq110(siteCrawlPages.runId, scope.runId),
75431
- eq110(siteCrawlPages.attemptId, scope.attemptId)
75639
+ const inScope = and94(
75640
+ eq111(siteCrawlPages.projectId, scope.projectId),
75641
+ eq111(siteCrawlPages.runId, scope.runId),
75642
+ eq111(siteCrawlPages.attemptId, scope.attemptId)
75432
75643
  );
75433
75644
  const groups = crawlReasonGroups(inScope);
75434
75645
  const total = groups.reduce((sum, group) => sum + group.pages, 0);
75435
75646
  const eligible = groups.reduce((sum, group) => sum + (group.inventoryEligible ? group.pages : 0), 0);
75436
- const urlVersions = app.db.select({ value: count8() }).from(siteCrawlPages).where(and93(inScope, isNotNull10(siteCrawlPages.urlVersionOf))).get()?.value ?? 0;
75437
- const urlVersionsNotFetched = app.db.select({ value: siteCrawlAttempts.urlVersionsSkipped }).from(siteCrawlAttempts).where(and93(
75438
- eq110(siteCrawlAttempts.projectId, scope.projectId),
75439
- eq110(siteCrawlAttempts.runId, scope.runId),
75440
- eq110(siteCrawlAttempts.id, scope.attemptId)
75647
+ const urlVersions = app.db.select({ value: count8() }).from(siteCrawlPages).where(and94(inScope, isNotNull10(siteCrawlPages.urlVersionOf))).get()?.value ?? 0;
75648
+ const urlVersionsNotFetched = app.db.select({ value: siteCrawlAttempts.urlVersionsSkipped }).from(siteCrawlAttempts).where(and94(
75649
+ eq111(siteCrawlAttempts.projectId, scope.projectId),
75650
+ eq111(siteCrawlAttempts.runId, scope.runId),
75651
+ eq111(siteCrawlAttempts.id, scope.attemptId)
75441
75652
  )).get()?.value ?? 0;
75442
75653
  return {
75443
75654
  scope: "selected-snapshot",
@@ -75457,33 +75668,33 @@ async function technicalAeoRoutes(app, opts) {
75457
75668
  const NO_CRAWL_COMPLETENESS = { complete: false, termination: null };
75458
75669
  const pageInScope = (scope, selector) => {
75459
75670
  if (!selector.nodeKey && !selector.url) return null;
75460
- return app.db.select().from(siteCrawlPages).where(and93(
75461
- eq110(siteCrawlPages.projectId, scope.projectId),
75462
- eq110(siteCrawlPages.runId, scope.runId),
75463
- eq110(siteCrawlPages.attemptId, scope.attemptId),
75464
- selector.nodeKey ? eq110(siteCrawlPages.nodeKey, selector.nodeKey) : eq110(siteCrawlPages.url, selector.url)
75671
+ return app.db.select().from(siteCrawlPages).where(and94(
75672
+ eq111(siteCrawlPages.projectId, scope.projectId),
75673
+ eq111(siteCrawlPages.runId, scope.runId),
75674
+ eq111(siteCrawlPages.attemptId, scope.attemptId),
75675
+ selector.nodeKey ? eq111(siteCrawlPages.nodeKey, selector.nodeKey) : eq111(siteCrawlPages.url, selector.url)
75465
75676
  )).orderBy(asc27(siteCrawlPages.nodeKey)).limit(1).get() ?? null;
75466
75677
  };
75467
- const rootPageInScope = (scope, rootUrl) => pageInScope(scope, { url: rootUrl }) ?? app.db.select().from(siteCrawlPages).where(and93(
75468
- eq110(siteCrawlPages.projectId, scope.projectId),
75469
- eq110(siteCrawlPages.runId, scope.runId),
75470
- eq110(siteCrawlPages.attemptId, scope.attemptId),
75471
- eq110(siteCrawlPages.depth, 0)
75678
+ const rootPageInScope = (scope, rootUrl) => pageInScope(scope, { url: rootUrl }) ?? app.db.select().from(siteCrawlPages).where(and94(
75679
+ eq111(siteCrawlPages.projectId, scope.projectId),
75680
+ eq111(siteCrawlPages.runId, scope.runId),
75681
+ eq111(siteCrawlPages.attemptId, scope.attemptId),
75682
+ eq111(siteCrawlPages.depth, 0)
75472
75683
  )).orderBy(asc27(siteCrawlPages.nodeKey)).limit(1).get() ?? null;
75473
- const isSurfaceableAuditRun = (projectId, runId) => Boolean(app.db.select({ id: runs.id }).from(runs).where(and93(
75474
- eq110(runs.id, runId),
75475
- eq110(runs.projectId, projectId),
75476
- eq110(runs.kind, RunKinds["site-audit"]),
75684
+ const isSurfaceableAuditRun = (projectId, runId) => Boolean(app.db.select({ id: runs.id }).from(runs).where(and94(
75685
+ eq111(runs.id, runId),
75686
+ eq111(runs.projectId, projectId),
75687
+ eq111(runs.kind, RunKinds["site-audit"]),
75477
75688
  inArray50(runs.status, SURFACEABLE_STATUSES),
75478
75689
  notProbeRun()
75479
75690
  )).limit(1).get());
75480
75691
  const assertKnownAuditRun = (projectId, runId) => {
75481
75692
  if (runId && !isSurfaceableAuditRun(projectId, runId)) throw notFound("Site crawl run", runId);
75482
75693
  };
75483
- const hasLegacyAudit = (projectId) => Boolean(app.db.select({ runId: siteAuditSnapshots.runId }).from(siteAuditSnapshots).innerJoin(runs, eq110(siteAuditSnapshots.runId, runs.id)).where(and93(
75484
- eq110(siteAuditSnapshots.projectId, projectId),
75485
- eq110(runs.projectId, projectId),
75486
- eq110(runs.kind, RunKinds["site-audit"]),
75694
+ const hasLegacyAudit = (projectId) => Boolean(app.db.select({ runId: siteAuditSnapshots.runId }).from(siteAuditSnapshots).innerJoin(runs, eq111(siteAuditSnapshots.runId, runs.id)).where(and94(
75695
+ eq111(siteAuditSnapshots.projectId, projectId),
75696
+ eq111(runs.projectId, projectId),
75697
+ eq111(runs.kind, RunKinds["site-audit"]),
75487
75698
  inArray50(runs.status, SURFACEABLE_STATUSES),
75488
75699
  notProbeRun()
75489
75700
  )).limit(1).get());
@@ -75585,9 +75796,9 @@ async function technicalAeoRoutes(app, opts) {
75585
75796
  };
75586
75797
  }
75587
75798
  const statusFilter = request.query.status === "success" || request.query.status === "error" ? request.query.status : null;
75588
- const conds = [eq110(siteAuditPages.projectId, project.id), eq110(siteAuditPages.runId, latest.snap.runId)];
75589
- if (statusFilter) conds.push(eq110(siteAuditPages.status, statusFilter));
75590
- const where = and93(...conds);
75799
+ const conds = [eq111(siteAuditPages.projectId, project.id), eq111(siteAuditPages.runId, latest.snap.runId)];
75800
+ if (statusFilter) conds.push(eq111(siteAuditPages.status, statusFilter));
75801
+ const where = and94(...conds);
75591
75802
  const totalRow = app.db.select({ value: count8() }).from(siteAuditPages).where(where).get();
75592
75803
  const total = totalRow?.value ?? 0;
75593
75804
  const limit = parsePositiveInt(request.query.limit, 100, 500);
@@ -75611,7 +75822,7 @@ async function technicalAeoRoutes(app, opts) {
75611
75822
  auditedAt: siteAuditSnapshots.auditedAt,
75612
75823
  aggregateScore: siteAuditSnapshots.aggregateScore,
75613
75824
  pagesAudited: siteAuditSnapshots.pagesAudited
75614
- }).from(siteAuditSnapshots).innerJoin(runs, eq110(siteAuditSnapshots.runId, runs.id)).where(and93(...surfaceableSiteAuditFilters(project.id))).orderBy(desc50(siteAuditSnapshots.createdAt)).limit(limit).all();
75825
+ }).from(siteAuditSnapshots).innerJoin(runs, eq111(siteAuditSnapshots.runId, runs.id)).where(and94(...surfaceableSiteAuditFilters(project.id))).orderBy(desc50(siteAuditSnapshots.createdAt)).limit(limit).all();
75615
75826
  return { project: project.name, points: rows.reverse() };
75616
75827
  });
75617
75828
  app.get("/projects/:name/technical-aeo/crawl", async (request) => {
@@ -75711,10 +75922,10 @@ async function technicalAeoRoutes(app, opts) {
75711
75922
  { projectId: project.id, runId: snapshot.runId, attemptId: snapshot.attemptId },
75712
75923
  snapshot.rootUrl
75713
75924
  )?.nodeKey ?? null;
75714
- const persistedLayout = app.db.select().from(siteCrawlGraphLayouts).where(and93(
75715
- eq110(siteCrawlGraphLayouts.projectId, project.id),
75716
- eq110(siteCrawlGraphLayouts.runId, snapshot.runId),
75717
- eq110(siteCrawlGraphLayouts.attemptId, snapshot.attemptId)
75925
+ const persistedLayout = app.db.select().from(siteCrawlGraphLayouts).where(and94(
75926
+ eq111(siteCrawlGraphLayouts.projectId, project.id),
75927
+ eq111(siteCrawlGraphLayouts.runId, snapshot.runId),
75928
+ eq111(siteCrawlGraphLayouts.attemptId, snapshot.attemptId)
75718
75929
  )).limit(1).get();
75719
75930
  if (!persistedLayout) {
75720
75931
  return {
@@ -75767,9 +75978,9 @@ async function technicalAeoRoutes(app, opts) {
75767
75978
  const maxNodes = parseBoundedLimit(request.query.maxNodes, SITE_CRAWL_GRAPH_DEFAULT_MAX_NODES, SITE_CRAWL_GRAPH_MAX_NODES);
75768
75979
  const maxEdges = parseBoundedLimit(request.query.maxEdges, SITE_CRAWL_GRAPH_DEFAULT_MAX_EDGES, SITE_CRAWL_GRAPH_MAX_EDGES);
75769
75980
  const graphScope = [
75770
- eq110(siteCrawlGraphNodes.projectId, project.id),
75771
- eq110(siteCrawlGraphNodes.runId, snapshot.runId),
75772
- eq110(siteCrawlGraphNodes.attemptId, snapshot.attemptId)
75981
+ eq111(siteCrawlGraphNodes.projectId, project.id),
75982
+ eq111(siteCrawlGraphNodes.runId, snapshot.runId),
75983
+ eq111(siteCrawlGraphNodes.attemptId, snapshot.attemptId)
75773
75984
  ];
75774
75985
  const nodeRows = app.db.select({
75775
75986
  nodeKey: siteCrawlPages.nodeKey,
@@ -75788,18 +75999,18 @@ async function technicalAeoRoutes(app, opts) {
75788
75999
  linkScoreNormalized: siteCrawlPages.linkScoreNormalized,
75789
76000
  x: siteCrawlGraphNodes.x,
75790
76001
  y: siteCrawlGraphNodes.y
75791
- }).from(siteCrawlGraphNodes).innerJoin(siteCrawlPages, and93(
75792
- eq110(siteCrawlPages.projectId, siteCrawlGraphNodes.projectId),
75793
- eq110(siteCrawlPages.runId, siteCrawlGraphNodes.runId),
75794
- eq110(siteCrawlPages.attemptId, siteCrawlGraphNodes.attemptId),
75795
- eq110(siteCrawlPages.nodeKey, siteCrawlGraphNodes.nodeKey)
75796
- )).where(and93(...graphScope, lt18(siteCrawlGraphNodes.sampleRank, maxNodes))).orderBy(asc27(siteCrawlGraphNodes.sampleRank)).all();
76002
+ }).from(siteCrawlGraphNodes).innerJoin(siteCrawlPages, and94(
76003
+ eq111(siteCrawlPages.projectId, siteCrawlGraphNodes.projectId),
76004
+ eq111(siteCrawlPages.runId, siteCrawlGraphNodes.runId),
76005
+ eq111(siteCrawlPages.attemptId, siteCrawlGraphNodes.attemptId),
76006
+ eq111(siteCrawlPages.nodeKey, siteCrawlGraphNodes.nodeKey)
76007
+ )).where(and94(...graphScope, lt19(siteCrawlGraphNodes.sampleRank, maxNodes))).orderBy(asc27(siteCrawlGraphNodes.sampleRank)).all();
75797
76008
  const nodes = nodeRows.map(({ indexabilityReasons, canonicalNodeKey, ...row }) => ({
75798
76009
  ...row,
75799
76010
  healthState: deriveSiteHealthState({ ...row, indexabilityReasons, canonicalNodeKey }),
75800
76011
  healthReason: deriveSiteHealthReason({ ...row, indexabilityReasons, canonicalNodeKey })
75801
76012
  }));
75802
- const graphLinkKindFilter = linkKind === SiteHealthLinkKinds.all ? void 0 : eq110(siteCrawlGraphEdges.isTemplate, linkKind === SiteHealthLinkKinds.template);
76013
+ const graphLinkKindFilter = linkKind === SiteHealthLinkKinds.all ? void 0 : eq111(siteCrawlGraphEdges.isTemplate, linkKind === SiteHealthLinkKinds.template);
75803
76014
  const edges = app.db.select({
75804
76015
  edgeKey: siteCrawlGraphEdges.edgeKey,
75805
76016
  sourceNodeKey: siteCrawlGraphEdges.sourceNodeKey,
@@ -75807,23 +76018,23 @@ async function technicalAeoRoutes(app, opts) {
75807
76018
  followable: siteCrawlGraphEdges.followable,
75808
76019
  occurrences: siteCrawlGraphEdges.occurrences,
75809
76020
  isTemplate: siteCrawlGraphEdges.isTemplate
75810
- }).from(siteCrawlGraphEdges).innerJoin(graphSourceNode, and93(
75811
- eq110(graphSourceNode.projectId, siteCrawlGraphEdges.projectId),
75812
- eq110(graphSourceNode.runId, siteCrawlGraphEdges.runId),
75813
- eq110(graphSourceNode.attemptId, siteCrawlGraphEdges.attemptId),
75814
- eq110(graphSourceNode.nodeKey, siteCrawlGraphEdges.sourceNodeKey),
75815
- lt18(graphSourceNode.sampleRank, maxNodes)
75816
- )).innerJoin(graphTargetNode, and93(
75817
- eq110(graphTargetNode.projectId, siteCrawlGraphEdges.projectId),
75818
- eq110(graphTargetNode.runId, siteCrawlGraphEdges.runId),
75819
- eq110(graphTargetNode.attemptId, siteCrawlGraphEdges.attemptId),
75820
- eq110(graphTargetNode.nodeKey, siteCrawlGraphEdges.targetNodeKey),
75821
- lt18(graphTargetNode.sampleRank, maxNodes)
75822
- )).where(and93(
75823
- eq110(siteCrawlGraphEdges.projectId, project.id),
75824
- eq110(siteCrawlGraphEdges.runId, snapshot.runId),
75825
- eq110(siteCrawlGraphEdges.attemptId, snapshot.attemptId),
75826
- lt18(siteCrawlGraphEdges.sampleRank, maxEdges),
76021
+ }).from(siteCrawlGraphEdges).innerJoin(graphSourceNode, and94(
76022
+ eq111(graphSourceNode.projectId, siteCrawlGraphEdges.projectId),
76023
+ eq111(graphSourceNode.runId, siteCrawlGraphEdges.runId),
76024
+ eq111(graphSourceNode.attemptId, siteCrawlGraphEdges.attemptId),
76025
+ eq111(graphSourceNode.nodeKey, siteCrawlGraphEdges.sourceNodeKey),
76026
+ lt19(graphSourceNode.sampleRank, maxNodes)
76027
+ )).innerJoin(graphTargetNode, and94(
76028
+ eq111(graphTargetNode.projectId, siteCrawlGraphEdges.projectId),
76029
+ eq111(graphTargetNode.runId, siteCrawlGraphEdges.runId),
76030
+ eq111(graphTargetNode.attemptId, siteCrawlGraphEdges.attemptId),
76031
+ eq111(graphTargetNode.nodeKey, siteCrawlGraphEdges.targetNodeKey),
76032
+ lt19(graphTargetNode.sampleRank, maxNodes)
76033
+ )).where(and94(
76034
+ eq111(siteCrawlGraphEdges.projectId, project.id),
76035
+ eq111(siteCrawlGraphEdges.runId, snapshot.runId),
76036
+ eq111(siteCrawlGraphEdges.attemptId, snapshot.attemptId),
76037
+ lt19(siteCrawlGraphEdges.sampleRank, maxEdges),
75827
76038
  graphLinkKindFilter
75828
76039
  )).orderBy(asc27(siteCrawlGraphEdges.sampleRank)).all();
75829
76040
  const totalNodes = persistedLayout.totalNodes;
@@ -75917,11 +76128,11 @@ async function technicalAeoRoutes(app, opts) {
75917
76128
  let frontier = [focus.nodeKey];
75918
76129
  let queryTruncated = false;
75919
76130
  for (let distance = 0; distance < hops && frontier.length > 0; distance += 1) {
75920
- const candidates = app.db.select().from(siteCrawlEdges).where(and93(
75921
- eq110(siteCrawlEdges.projectId, scope.projectId),
75922
- eq110(siteCrawlEdges.runId, scope.runId),
75923
- eq110(siteCrawlEdges.attemptId, scope.attemptId),
75924
- eq110(siteCrawlEdges.internal, true),
76131
+ const candidates = app.db.select().from(siteCrawlEdges).where(and94(
76132
+ eq111(siteCrawlEdges.projectId, scope.projectId),
76133
+ eq111(siteCrawlEdges.runId, scope.runId),
76134
+ eq111(siteCrawlEdges.attemptId, scope.attemptId),
76135
+ eq111(siteCrawlEdges.internal, true),
75925
76136
  isNotNull10(siteCrawlEdges.targetNodeKey),
75926
76137
  or21(
75927
76138
  inArray50(siteCrawlEdges.sourceNodeKey, frontier),
@@ -75958,19 +76169,19 @@ async function technicalAeoRoutes(app, opts) {
75958
76169
  frontier = [...next].sort();
75959
76170
  }
75960
76171
  const nodeKeys = [...distances.keys()];
75961
- const pageRows = nodeKeys.length === 0 ? [] : app.db.select().from(siteCrawlPages).where(and93(
75962
- eq110(siteCrawlPages.projectId, scope.projectId),
75963
- eq110(siteCrawlPages.runId, scope.runId),
75964
- eq110(siteCrawlPages.attemptId, scope.attemptId),
76172
+ const pageRows = nodeKeys.length === 0 ? [] : app.db.select().from(siteCrawlPages).where(and94(
76173
+ eq111(siteCrawlPages.projectId, scope.projectId),
76174
+ eq111(siteCrawlPages.runId, scope.runId),
76175
+ eq111(siteCrawlPages.attemptId, scope.attemptId),
75965
76176
  inArray50(siteCrawlPages.nodeKey, nodeKeys)
75966
76177
  )).all();
75967
76178
  const persistedNodeKeys = new Set(pageRows.map((row) => row.nodeKey));
75968
76179
  const outermostNodeKeys = pageRows.filter((row) => distances.get(row.nodeKey) === hops).map((row) => row.nodeKey);
75969
- const omittedOutermostEdge = outermostNodeKeys.length === 0 ? void 0 : app.db.select({ edgeKey: siteCrawlEdges.edgeKey }).from(siteCrawlEdges).where(and93(
75970
- eq110(siteCrawlEdges.projectId, scope.projectId),
75971
- eq110(siteCrawlEdges.runId, scope.runId),
75972
- eq110(siteCrawlEdges.attemptId, scope.attemptId),
75973
- eq110(siteCrawlEdges.internal, true),
76180
+ const omittedOutermostEdge = outermostNodeKeys.length === 0 ? void 0 : app.db.select({ edgeKey: siteCrawlEdges.edgeKey }).from(siteCrawlEdges).where(and94(
76181
+ eq111(siteCrawlEdges.projectId, scope.projectId),
76182
+ eq111(siteCrawlEdges.runId, scope.runId),
76183
+ eq111(siteCrawlEdges.attemptId, scope.attemptId),
76184
+ eq111(siteCrawlEdges.internal, true),
75974
76185
  isNotNull10(siteCrawlEdges.targetNodeKey),
75975
76186
  inArray50(siteCrawlEdges.sourceNodeKey, outermostNodeKeys),
75976
76187
  inArray50(siteCrawlEdges.targetNodeKey, outermostNodeKeys)
@@ -76066,22 +76277,22 @@ async function technicalAeoRoutes(app, opts) {
76066
76277
  let found = fromPage.nodeKey === toPage.nodeKey;
76067
76278
  let truncated = false;
76068
76279
  for (let depth = 0; depth < maxDepth && frontier.length > 0 && !found; depth += 1) {
76069
- const candidates = app.db.select().from(siteCrawlEdges).where(and93(
76070
- eq110(siteCrawlEdges.projectId, scope.projectId),
76071
- eq110(siteCrawlEdges.runId, scope.runId),
76072
- eq110(siteCrawlEdges.attemptId, scope.attemptId),
76073
- eq110(siteCrawlEdges.internal, true),
76074
- eq110(siteCrawlEdges.followable, true),
76075
- eq110(siteCrawlEdges.relation, "anchor"),
76280
+ const candidates = app.db.select().from(siteCrawlEdges).where(and94(
76281
+ eq111(siteCrawlEdges.projectId, scope.projectId),
76282
+ eq111(siteCrawlEdges.runId, scope.runId),
76283
+ eq111(siteCrawlEdges.attemptId, scope.attemptId),
76284
+ eq111(siteCrawlEdges.internal, true),
76285
+ eq111(siteCrawlEdges.followable, true),
76286
+ eq111(siteCrawlEdges.relation, "anchor"),
76076
76287
  isNotNull10(siteCrawlEdges.targetNodeKey),
76077
76288
  inArray50(siteCrawlEdges.sourceNodeKey, frontier)
76078
76289
  )).orderBy(asc27(siteCrawlEdges.edgeKey)).limit(SITE_HEALTH_PATH_MAX_VISITED_NODES + 1).all();
76079
76290
  if (candidates.length > SITE_HEALTH_PATH_MAX_VISITED_NODES) truncated = true;
76080
76291
  const candidateTargetKeys = [...new Set(candidates.map((edge) => edge.targetNodeKey).filter((nodeKey) => nodeKey != null))];
76081
- const persistedTargetKeys = new Set(candidateTargetKeys.length === 0 ? [] : app.db.select({ nodeKey: siteCrawlPages.nodeKey }).from(siteCrawlPages).where(and93(
76082
- eq110(siteCrawlPages.projectId, scope.projectId),
76083
- eq110(siteCrawlPages.runId, scope.runId),
76084
- eq110(siteCrawlPages.attemptId, scope.attemptId),
76292
+ const persistedTargetKeys = new Set(candidateTargetKeys.length === 0 ? [] : app.db.select({ nodeKey: siteCrawlPages.nodeKey }).from(siteCrawlPages).where(and94(
76293
+ eq111(siteCrawlPages.projectId, scope.projectId),
76294
+ eq111(siteCrawlPages.runId, scope.runId),
76295
+ eq111(siteCrawlPages.attemptId, scope.attemptId),
76085
76296
  inArray50(siteCrawlPages.nodeKey, candidateTargetKeys)
76086
76297
  )).all().map((row) => row.nodeKey));
76087
76298
  const next = /* @__PURE__ */ new Set();
@@ -76123,10 +76334,10 @@ async function technicalAeoRoutes(app, opts) {
76123
76334
  pathEdges.unshift(step.edge);
76124
76335
  pathKeys.unshift(step.nodeKey);
76125
76336
  }
76126
- const pathRows = app.db.select().from(siteCrawlPages).where(and93(
76127
- eq110(siteCrawlPages.projectId, scope.projectId),
76128
- eq110(siteCrawlPages.runId, scope.runId),
76129
- eq110(siteCrawlPages.attemptId, scope.attemptId),
76337
+ const pathRows = app.db.select().from(siteCrawlPages).where(and94(
76338
+ eq111(siteCrawlPages.projectId, scope.projectId),
76339
+ eq111(siteCrawlPages.runId, scope.runId),
76340
+ eq111(siteCrawlPages.attemptId, scope.attemptId),
76130
76341
  inArray50(siteCrawlPages.nodeKey, pathKeys)
76131
76342
  )).all();
76132
76343
  const pageByKey = new Map(pathRows.map((row) => [row.nodeKey, row]));
@@ -76155,22 +76366,22 @@ async function technicalAeoRoutes(app, opts) {
76155
76366
  throw validationError("change must be all, added, removed, or changed");
76156
76367
  }
76157
76368
  const completeSnapshotFilters = [
76158
- eq110(siteCrawlSnapshots.projectId, project.id),
76159
- eq110(runs.projectId, project.id),
76160
- eq110(runs.kind, RunKinds["site-audit"]),
76161
- eq110(runs.status, RunStatuses.completed),
76162
- eq110(siteCrawlSnapshots.complete, true),
76369
+ eq111(siteCrawlSnapshots.projectId, project.id),
76370
+ eq111(runs.projectId, project.id),
76371
+ eq111(runs.kind, RunKinds["site-audit"]),
76372
+ eq111(runs.status, RunStatuses.completed),
76373
+ eq111(siteCrawlSnapshots.complete, true),
76163
76374
  notProbeRun()
76164
76375
  ];
76165
- const afterTarget = request.query.toRunId ? resolveCrawl(project.id, request.query.toRunId) : app.db.select({ snapshot: siteCrawlSnapshots, runStatus: runs.status }).from(siteCrawlSnapshots).innerJoin(runs, eq110(siteCrawlSnapshots.runId, runs.id)).where(and93(...completeSnapshotFilters)).orderBy(desc50(siteCrawlSnapshots.createdAt), desc50(siteCrawlSnapshots.runId)).limit(1).get();
76376
+ const afterTarget = request.query.toRunId ? resolveCrawl(project.id, request.query.toRunId) : app.db.select({ snapshot: siteCrawlSnapshots, runStatus: runs.status }).from(siteCrawlSnapshots).innerJoin(runs, eq111(siteCrawlSnapshots.runId, runs.id)).where(and94(...completeSnapshotFilters)).orderBy(desc50(siteCrawlSnapshots.createdAt), desc50(siteCrawlSnapshots.runId)).limit(1).get();
76166
76377
  if (request.query.toRunId && !afterTarget) throw notFound("Site crawl run", request.query.toRunId);
76167
- const beforeTarget = request.query.fromRunId ? resolveCrawl(project.id, request.query.fromRunId) : afterTarget ? app.db.select({ snapshot: siteCrawlSnapshots, runStatus: runs.status }).from(siteCrawlSnapshots).innerJoin(runs, eq110(siteCrawlSnapshots.runId, runs.id)).where(and93(
76378
+ const beforeTarget = request.query.fromRunId ? resolveCrawl(project.id, request.query.fromRunId) : afterTarget ? app.db.select({ snapshot: siteCrawlSnapshots, runStatus: runs.status }).from(siteCrawlSnapshots).innerJoin(runs, eq111(siteCrawlSnapshots.runId, runs.id)).where(and94(
76168
76379
  ...completeSnapshotFilters,
76169
76380
  or21(
76170
- lt18(siteCrawlSnapshots.createdAt, afterTarget.snapshot.createdAt),
76171
- and93(
76172
- eq110(siteCrawlSnapshots.createdAt, afterTarget.snapshot.createdAt),
76173
- lt18(siteCrawlSnapshots.runId, afterTarget.snapshot.runId)
76381
+ lt19(siteCrawlSnapshots.createdAt, afterTarget.snapshot.createdAt),
76382
+ and94(
76383
+ eq111(siteCrawlSnapshots.createdAt, afterTarget.snapshot.createdAt),
76384
+ lt19(siteCrawlSnapshots.runId, afterTarget.snapshot.runId)
76174
76385
  )
76175
76386
  )
76176
76387
  )).orderBy(desc50(siteCrawlSnapshots.createdAt), desc50(siteCrawlSnapshots.runId)).limit(1).get() : void 0;
@@ -76414,17 +76625,17 @@ async function technicalAeoRoutes(app, opts) {
76414
76625
  const visibleKeys = keyRows.slice(0, limit);
76415
76626
  const pageKeys = visibleKeys.filter((row) => row.entity === "page").map((row) => row.key);
76416
76627
  const linkKeys = visibleKeys.filter((row) => row.entity === "link").map((row) => row.key);
76417
- const pageRowsFor = (scope) => pageKeys.length === 0 ? [] : app.db.select().from(siteCrawlPages).where(and93(
76418
- eq110(siteCrawlPages.projectId, scope.projectId),
76419
- eq110(siteCrawlPages.runId, scope.runId),
76420
- eq110(siteCrawlPages.attemptId, scope.attemptId),
76628
+ const pageRowsFor = (scope) => pageKeys.length === 0 ? [] : app.db.select().from(siteCrawlPages).where(and94(
76629
+ eq111(siteCrawlPages.projectId, scope.projectId),
76630
+ eq111(siteCrawlPages.runId, scope.runId),
76631
+ eq111(siteCrawlPages.attemptId, scope.attemptId),
76421
76632
  inArray50(siteCrawlPages.nodeKey, pageKeys)
76422
76633
  )).all();
76423
- const edgeRowsFor = (scope) => linkKeys.length === 0 ? [] : app.db.select().from(siteCrawlEdges).where(and93(
76424
- eq110(siteCrawlEdges.projectId, scope.projectId),
76425
- eq110(siteCrawlEdges.runId, scope.runId),
76426
- eq110(siteCrawlEdges.attemptId, scope.attemptId),
76427
- eq110(siteCrawlEdges.internal, true),
76634
+ const edgeRowsFor = (scope) => linkKeys.length === 0 ? [] : app.db.select().from(siteCrawlEdges).where(and94(
76635
+ eq111(siteCrawlEdges.projectId, scope.projectId),
76636
+ eq111(siteCrawlEdges.runId, scope.runId),
76637
+ eq111(siteCrawlEdges.attemptId, scope.attemptId),
76638
+ eq111(siteCrawlEdges.internal, true),
76428
76639
  inArray50(siteCrawlEdges.edgeKey, linkKeys)
76429
76640
  )).all();
76430
76641
  const beforePages = new Map(pageRowsFor(beforeScope).map((row) => [row.nodeKey, mapCrawlPage(row)]));
@@ -76598,16 +76809,16 @@ async function technicalAeoRoutes(app, opts) {
76598
76809
  }
76599
76810
  const inventorySummary = inventorySummaryFor(detailScopeFor(project.id, snapshot));
76600
76811
  const filters = [
76601
- eq110(siteCrawlPages.projectId, project.id),
76602
- eq110(siteCrawlPages.runId, snapshot.runId),
76603
- eq110(siteCrawlPages.attemptId, snapshot.attemptId)
76812
+ eq111(siteCrawlPages.projectId, project.id),
76813
+ eq111(siteCrawlPages.runId, snapshot.runId),
76814
+ eq111(siteCrawlPages.attemptId, snapshot.attemptId)
76604
76815
  ];
76605
- if (request.query.nodeKey) filters.push(eq110(siteCrawlPages.nodeKey, request.query.nodeKey));
76816
+ if (request.query.nodeKey) filters.push(eq111(siteCrawlPages.nodeKey, request.query.nodeKey));
76606
76817
  const inventoryEligible = parseBoolean2(request.query.inventoryEligible);
76607
- if (inventoryEligible != null) filters.push(eq110(siteCrawlPages.inventoryEligible, inventoryEligible));
76608
- if (request.query.fetchState) filters.push(eq110(siteCrawlPages.fetchState, request.query.fetchState));
76609
- if (request.query.indexabilityState) filters.push(eq110(siteCrawlPages.indexabilityState, request.query.indexabilityState));
76610
- if (request.query.auditState) filters.push(eq110(siteCrawlPages.auditState, request.query.auditState));
76818
+ if (inventoryEligible != null) filters.push(eq111(siteCrawlPages.inventoryEligible, inventoryEligible));
76819
+ if (request.query.fetchState) filters.push(eq111(siteCrawlPages.fetchState, request.query.fetchState));
76820
+ if (request.query.indexabilityState) filters.push(eq111(siteCrawlPages.indexabilityState, request.query.indexabilityState));
76821
+ if (request.query.auditState) filters.push(eq111(siteCrawlPages.auditState, request.query.auditState));
76611
76822
  const urlVersions = parseUrlVersionFilter(request.query.urlVersions);
76612
76823
  if (urlVersions === SiteCrawlUrlVersionFilters.exclude) filters.push(isNull17(siteCrawlPages.urlVersionOf));
76613
76824
  if (urlVersions === SiteCrawlUrlVersionFilters.only) filters.push(isNotNull10(siteCrawlPages.urlVersionOf));
@@ -76633,14 +76844,14 @@ async function technicalAeoRoutes(app, opts) {
76633
76844
  const orderBy = sort === "score-desc" ? [desc50(siteCrawlPages.auditScore), asc27(siteCrawlPages.nodeKey)] : sort === "score-asc" ? [asc27(siteCrawlPages.auditScore), asc27(siteCrawlPages.nodeKey)] : sort === "path" ? [asc27(siteCrawlPages.path), asc27(siteCrawlPages.nodeKey)] : [asc27(siteCrawlPages.url), asc27(siteCrawlPages.nodeKey)];
76634
76845
  let healthStateFilter = null;
76635
76846
  if (healthState) {
76636
- const legacyRow = app.db.select({ id: siteCrawlPages.id }).from(siteCrawlPages).where(and93(
76637
- eq110(siteCrawlPages.projectId, project.id),
76638
- eq110(siteCrawlPages.runId, snapshot.runId),
76639
- eq110(siteCrawlPages.attemptId, snapshot.attemptId),
76847
+ const legacyRow = app.db.select({ id: siteCrawlPages.id }).from(siteCrawlPages).where(and94(
76848
+ eq111(siteCrawlPages.projectId, project.id),
76849
+ eq111(siteCrawlPages.runId, snapshot.runId),
76850
+ eq111(siteCrawlPages.attemptId, snapshot.attemptId),
76640
76851
  isNull17(siteCrawlPages.healthState)
76641
76852
  )).limit(1).get();
76642
76853
  healthStateFilter = legacyRow ? "unavailable-legacy-scan" : "applied";
76643
- if (healthStateFilter === "applied") filters.push(eq110(siteCrawlPages.healthState, healthState));
76854
+ if (healthStateFilter === "applied") filters.push(eq111(siteCrawlPages.healthState, healthState));
76644
76855
  }
76645
76856
  if (healthStateFilter === "unavailable-legacy-scan") {
76646
76857
  return {
@@ -76657,7 +76868,7 @@ async function technicalAeoRoutes(app, opts) {
76657
76868
  pages: []
76658
76869
  };
76659
76870
  }
76660
- const where = and93(...filters);
76871
+ const where = and94(...filters);
76661
76872
  const total = app.db.select({ value: count8() }).from(siteCrawlPages).where(where).get()?.value ?? 0;
76662
76873
  const rows = app.db.select().from(siteCrawlPages).where(where).orderBy(...orderBy).limit(limit).offset(offset).all();
76663
76874
  const nextOffset = offset + rows.length;
@@ -76692,10 +76903,10 @@ async function technicalAeoRoutes(app, opts) {
76692
76903
  url: siteCrawlPages.url,
76693
76904
  inventoryEligible: siteCrawlPages.inventoryEligible,
76694
76905
  fetchState: siteCrawlPages.fetchState
76695
- }).from(siteCrawlPages).where(and93(
76696
- eq110(siteCrawlPages.projectId, project.id),
76697
- eq110(siteCrawlPages.runId, snapshot.runId),
76698
- eq110(siteCrawlPages.attemptId, snapshot.attemptId)
76906
+ }).from(siteCrawlPages).where(and94(
76907
+ eq111(siteCrawlPages.projectId, project.id),
76908
+ eq111(siteCrawlPages.runId, snapshot.runId),
76909
+ eq111(siteCrawlPages.attemptId, snapshot.attemptId)
76699
76910
  )).limit(MAX_STRUCTURE_SOURCE_ROWS + 1).all();
76700
76911
  if (sourceRows.length > MAX_STRUCTURE_SOURCE_ROWS) {
76701
76912
  throw validationError(`Persisted crawl exceeds the ${MAX_STRUCTURE_SOURCE_ROWS}-page structure limit`);
@@ -76777,17 +76988,17 @@ async function technicalAeoRoutes(app, opts) {
76777
76988
  };
76778
76989
  }
76779
76990
  const filters = [
76780
- eq110(siteCrawlEdges.projectId, project.id),
76781
- eq110(siteCrawlEdges.runId, snapshot.runId),
76782
- eq110(siteCrawlEdges.attemptId, snapshot.attemptId),
76783
- eq110(siteCrawlEdges.internal, true),
76991
+ eq111(siteCrawlEdges.projectId, project.id),
76992
+ eq111(siteCrawlEdges.runId, snapshot.runId),
76993
+ eq111(siteCrawlEdges.attemptId, snapshot.attemptId),
76994
+ eq111(siteCrawlEdges.internal, true),
76784
76995
  linkKindFilter(linkKind)
76785
76996
  ];
76786
- if (request.query.sourceUrl) filters.push(eq110(siteCrawlEdges.sourceUrl, request.query.sourceUrl));
76787
- if (request.query.targetUrl) filters.push(eq110(siteCrawlEdges.targetUrl, request.query.targetUrl));
76997
+ if (request.query.sourceUrl) filters.push(eq111(siteCrawlEdges.sourceUrl, request.query.sourceUrl));
76998
+ if (request.query.targetUrl) filters.push(eq111(siteCrawlEdges.targetUrl, request.query.targetUrl));
76788
76999
  const followable = parseBoolean2(request.query.followable);
76789
- if (followable != null) filters.push(eq110(siteCrawlEdges.followable, followable));
76790
- const where = and93(...filters);
77000
+ if (followable != null) filters.push(eq111(siteCrawlEdges.followable, followable));
77001
+ const where = and94(...filters);
76791
77002
  const total = app.db.select({ value: count8() }).from(siteCrawlEdges).where(where).get()?.value ?? 0;
76792
77003
  const limit = parseBoundedLimit(request.query.limit, 100, 200);
76793
77004
  const offset = decodeCursor(request.query.cursor);
@@ -76848,17 +77059,17 @@ async function technicalAeoRoutes(app, opts) {
76848
77059
  };
76849
77060
  }
76850
77061
  const scope = [
76851
- eq110(siteCrawlEdges.projectId, project.id),
76852
- eq110(siteCrawlEdges.runId, snapshot.runId),
76853
- eq110(siteCrawlEdges.attemptId, snapshot.attemptId),
76854
- eq110(siteCrawlEdges.internal, true),
77062
+ eq111(siteCrawlEdges.projectId, project.id),
77063
+ eq111(siteCrawlEdges.runId, snapshot.runId),
77064
+ eq111(siteCrawlEdges.attemptId, snapshot.attemptId),
77065
+ eq111(siteCrawlEdges.internal, true),
76855
77066
  linkKindFilter(linkKind)
76856
77067
  ];
76857
- const inboundMatch = request.query.nodeKey && request.query.url ? or21(eq110(siteCrawlEdges.targetNodeKey, request.query.nodeKey), eq110(siteCrawlEdges.targetUrl, request.query.url)) : request.query.nodeKey ? eq110(siteCrawlEdges.targetNodeKey, request.query.nodeKey) : eq110(siteCrawlEdges.targetUrl, request.query.url);
76858
- const outboundMatch = request.query.nodeKey && request.query.url ? or21(eq110(siteCrawlEdges.sourceNodeKey, request.query.nodeKey), eq110(siteCrawlEdges.sourceUrl, request.query.url)) : request.query.nodeKey ? eq110(siteCrawlEdges.sourceNodeKey, request.query.nodeKey) : eq110(siteCrawlEdges.sourceUrl, request.query.url);
77068
+ const inboundMatch = request.query.nodeKey && request.query.url ? or21(eq111(siteCrawlEdges.targetNodeKey, request.query.nodeKey), eq111(siteCrawlEdges.targetUrl, request.query.url)) : request.query.nodeKey ? eq111(siteCrawlEdges.targetNodeKey, request.query.nodeKey) : eq111(siteCrawlEdges.targetUrl, request.query.url);
77069
+ const outboundMatch = request.query.nodeKey && request.query.url ? or21(eq111(siteCrawlEdges.sourceNodeKey, request.query.nodeKey), eq111(siteCrawlEdges.sourceUrl, request.query.url)) : request.query.nodeKey ? eq111(siteCrawlEdges.sourceNodeKey, request.query.nodeKey) : eq111(siteCrawlEdges.sourceUrl, request.query.url);
76859
77070
  const limit = parseBoundedLimit(request.query.limit, 50, 100);
76860
- const inboundRows = app.db.select().from(siteCrawlEdges).where(and93(...scope, inboundMatch)).orderBy(asc27(siteCrawlEdges.edgeKey)).limit(limit + 1).all();
76861
- const outboundRows = app.db.select().from(siteCrawlEdges).where(and93(...scope, outboundMatch)).orderBy(asc27(siteCrawlEdges.edgeKey)).limit(limit + 1).all();
77071
+ const inboundRows = app.db.select().from(siteCrawlEdges).where(and94(...scope, inboundMatch)).orderBy(asc27(siteCrawlEdges.edgeKey)).limit(limit + 1).all();
77072
+ const outboundRows = app.db.select().from(siteCrawlEdges).where(and94(...scope, outboundMatch)).orderBy(asc27(siteCrawlEdges.edgeKey)).limit(limit + 1).all();
76862
77073
  return {
76863
77074
  project: project.name,
76864
77075
  hasCrawlData: true,
@@ -76892,11 +77103,11 @@ async function technicalAeoRoutes(app, opts) {
76892
77103
  if (!snapshot.attemptId) {
76893
77104
  return { project: project.name, runId: snapshot.runId, state: "partial", checkDeadLinks: true, checked: snapshot.deadLinksChecked, found: snapshot.deadLinksFound, unverified: snapshot.deadLinksUnverified, total: 0, nextCursor: null, deadLinks: [], runSelection: target.runSelection };
76894
77105
  }
76895
- const where = and93(
76896
- eq110(siteCrawlFindings.projectId, project.id),
76897
- eq110(siteCrawlFindings.runId, snapshot.runId),
76898
- eq110(siteCrawlFindings.attemptId, snapshot.attemptId),
76899
- eq110(siteCrawlFindings.findingType, "dead-link"),
77106
+ const where = and94(
77107
+ eq111(siteCrawlFindings.projectId, project.id),
77108
+ eq111(siteCrawlFindings.runId, snapshot.runId),
77109
+ eq111(siteCrawlFindings.attemptId, snapshot.attemptId),
77110
+ eq111(siteCrawlFindings.findingType, "dead-link"),
76900
77111
  // Belt and braces on the client-facing surface: a row without a status
76901
77112
  // code is not evidence of a broken link, and this response is where such
76902
77113
  // a row would be read as one. Migration 140 removed every stored row that
@@ -76946,16 +77157,16 @@ async function technicalAeoRoutes(app, opts) {
76946
77157
  createdAt: runs.createdAt,
76947
77158
  startedAt: runs.startedAt,
76948
77159
  finishedAt: runs.finishedAt
76949
- }).from(runs).where(and93(
76950
- eq110(runs.projectId, project.id),
76951
- eq110(runs.kind, RunKinds["site-audit"]),
77160
+ }).from(runs).where(and94(
77161
+ eq111(runs.projectId, project.id),
77162
+ eq111(runs.kind, RunKinds["site-audit"]),
76952
77163
  notProbeRun()
76953
77164
  )).orderBy(desc50(runs.createdAt), desc50(runs.id)).limit(limit).all();
76954
77165
  const runIds = rows.map((row) => row.runId);
76955
- const crawlRunIds = new Set(runIds.length === 0 ? [] : app.db.select({ runId: siteCrawlSnapshots.runId }).from(siteCrawlSnapshots).innerJoin(runs, eq110(siteCrawlSnapshots.runId, runs.id)).where(and93(
76956
- eq110(siteCrawlSnapshots.projectId, project.id),
76957
- eq110(runs.projectId, project.id),
76958
- eq110(runs.kind, RunKinds["site-audit"]),
77166
+ const crawlRunIds = new Set(runIds.length === 0 ? [] : app.db.select({ runId: siteCrawlSnapshots.runId }).from(siteCrawlSnapshots).innerJoin(runs, eq111(siteCrawlSnapshots.runId, runs.id)).where(and94(
77167
+ eq111(siteCrawlSnapshots.projectId, project.id),
77168
+ eq111(runs.projectId, project.id),
77169
+ eq111(runs.kind, RunKinds["site-audit"]),
76959
77170
  inArray50(runs.status, SURFACEABLE_STATUSES),
76960
77171
  notProbeRun(),
76961
77172
  inArray50(siteCrawlSnapshots.runId, runIds)
@@ -76972,21 +77183,21 @@ async function technicalAeoRoutes(app, opts) {
76972
77183
  });
76973
77184
  app.get("/projects/:name/technical-aeo/runs/:runId/progress", async (request) => {
76974
77185
  const project = resolveProject(app.db, request.params.name);
76975
- const run = app.db.select().from(runs).where(and93(
76976
- eq110(runs.id, request.params.runId),
76977
- eq110(runs.projectId, project.id),
76978
- eq110(runs.kind, RunKinds["site-audit"]),
77186
+ const run = app.db.select().from(runs).where(and94(
77187
+ eq111(runs.id, request.params.runId),
77188
+ eq111(runs.projectId, project.id),
77189
+ eq111(runs.kind, RunKinds["site-audit"]),
76979
77190
  notProbeRun()
76980
77191
  )).get();
76981
77192
  if (!run) throw notFound("Site audit run", request.params.runId);
76982
- const attempt = app.db.select().from(siteCrawlAttempts).where(and93(
76983
- eq110(siteCrawlAttempts.projectId, project.id),
76984
- eq110(siteCrawlAttempts.runId, run.id)
77193
+ const attempt = app.db.select().from(siteCrawlAttempts).where(and94(
77194
+ eq111(siteCrawlAttempts.projectId, project.id),
77195
+ eq111(siteCrawlAttempts.runId, run.id)
76985
77196
  )).orderBy(desc50(siteCrawlAttempts.attemptNumber), desc50(siteCrawlAttempts.updatedAt)).limit(1).get();
76986
- const persistedLayout = attempt ? app.db.select().from(siteCrawlGraphLayouts).where(and93(
76987
- eq110(siteCrawlGraphLayouts.projectId, project.id),
76988
- eq110(siteCrawlGraphLayouts.runId, run.id),
76989
- eq110(siteCrawlGraphLayouts.attemptId, attempt.id)
77197
+ const persistedLayout = attempt ? app.db.select().from(siteCrawlGraphLayouts).where(and94(
77198
+ eq111(siteCrawlGraphLayouts.projectId, project.id),
77199
+ eq111(siteCrawlGraphLayouts.runId, run.id),
77200
+ eq111(siteCrawlGraphLayouts.attemptId, attempt.id)
76990
77201
  )).get() : void 0;
76991
77202
  const layoutState = persistedLayout?.state === "ready" || persistedLayout?.state === "unavailable" ? persistedLayout.state : run.status === RunStatuses.completed || run.status === RunStatuses.partial ? "unavailable" : "pending";
76992
77203
  return {
@@ -77017,18 +77228,18 @@ async function technicalAeoRoutes(app, opts) {
77017
77228
  };
77018
77229
  });
77019
77230
  app.get("/projects/:name/technical-aeo/runs/:runId/page-health-preview", async (request) => app.db.transaction((tx) => {
77020
- const project = tx.select().from(projects).where(eq110(projects.name, request.params.name)).get();
77231
+ const project = tx.select().from(projects).where(eq111(projects.name, request.params.name)).get();
77021
77232
  if (!project) throw notFound("Project", request.params.name);
77022
- const run = tx.select().from(runs).where(and93(
77023
- eq110(runs.id, request.params.runId),
77024
- eq110(runs.projectId, project.id),
77025
- eq110(runs.kind, RunKinds["site-audit"]),
77233
+ const run = tx.select().from(runs).where(and94(
77234
+ eq111(runs.id, request.params.runId),
77235
+ eq111(runs.projectId, project.id),
77236
+ eq111(runs.kind, RunKinds["site-audit"]),
77026
77237
  notProbeRun()
77027
77238
  )).get();
77028
77239
  if (!run) throw notFound("Site audit run", request.params.runId);
77029
- const attempt = tx.select().from(siteCrawlAttempts).where(and93(
77030
- eq110(siteCrawlAttempts.projectId, project.id),
77031
- eq110(siteCrawlAttempts.runId, run.id)
77240
+ const attempt = tx.select().from(siteCrawlAttempts).where(and94(
77241
+ eq111(siteCrawlAttempts.projectId, project.id),
77242
+ eq111(siteCrawlAttempts.runId, run.id)
77032
77243
  )).orderBy(desc50(siteCrawlAttempts.attemptNumber), desc50(siteCrawlAttempts.updatedAt)).limit(1).get();
77033
77244
  const state = run.status === RunStatuses.queued ? "waiting" : run.status === RunStatuses.running ? "collecting" : "terminal";
77034
77245
  const base = {
@@ -77042,11 +77253,11 @@ async function technicalAeoRoutes(app, opts) {
77042
77253
  if (!attempt) {
77043
77254
  return { ...base, pagesAudited: 0, examples: [] };
77044
77255
  }
77045
- const auditedWhere = and93(
77046
- eq110(siteCrawlPages.projectId, project.id),
77047
- eq110(siteCrawlPages.runId, run.id),
77048
- eq110(siteCrawlPages.attemptId, attempt.id),
77049
- eq110(siteCrawlPages.auditState, "success")
77256
+ const auditedWhere = and94(
77257
+ eq111(siteCrawlPages.projectId, project.id),
77258
+ eq111(siteCrawlPages.runId, run.id),
77259
+ eq111(siteCrawlPages.attemptId, attempt.id),
77260
+ eq111(siteCrawlPages.auditState, "success")
77050
77261
  );
77051
77262
  const pagesAudited = tx.select({ value: count8() }).from(siteCrawlPages).where(auditedWhere).get()?.value ?? 0;
77052
77263
  if (state !== "collecting") {
@@ -77057,10 +77268,10 @@ async function technicalAeoRoutes(app, opts) {
77057
77268
  url: siteCrawlPages.url,
77058
77269
  auditScore: siteCrawlPages.auditScore,
77059
77270
  auditFields: siteCrawlPages.auditFields
77060
- }).from(siteCrawlPages).where(and93(
77271
+ }).from(siteCrawlPages).where(and94(
77061
77272
  auditedWhere,
77062
77273
  isNotNull10(siteCrawlPages.auditScore),
77063
- lt18(siteCrawlPages.auditScore, 70)
77274
+ lt19(siteCrawlPages.auditScore, 70)
77064
77275
  )).orderBy(asc27(siteCrawlPages.auditScore), asc27(siteCrawlPages.nodeKey)).limit(LIVE_PAGE_HEALTH_CANDIDATE_LIMIT).all();
77065
77276
  const examples = candidates.flatMap((candidate) => {
77066
77277
  if (candidate.auditScore == null) return [];
@@ -77093,12 +77304,12 @@ async function technicalAeoRoutes(app, opts) {
77093
77304
  status: runs.status,
77094
77305
  identityKey: siteCrawlRunRequests.identityKey,
77095
77306
  effectiveOptions: siteCrawlRunRequests.effectiveOptions
77096
- }).from(runs).leftJoin(siteCrawlRunRequests, and93(
77097
- eq110(siteCrawlRunRequests.projectId, runs.projectId),
77098
- eq110(siteCrawlRunRequests.runId, runs.id)
77099
- )).where(and93(
77100
- eq110(runs.projectId, project.id),
77101
- eq110(runs.kind, RunKinds["site-audit"]),
77307
+ }).from(runs).leftJoin(siteCrawlRunRequests, and94(
77308
+ eq111(siteCrawlRunRequests.projectId, runs.projectId),
77309
+ eq111(siteCrawlRunRequests.runId, runs.id)
77310
+ )).where(and94(
77311
+ eq111(runs.projectId, project.id),
77312
+ eq111(runs.kind, RunKinds["site-audit"]),
77102
77313
  inArray50(runs.status, [RunStatuses.queued, RunStatuses.running])
77103
77314
  )).get();
77104
77315
  if (existing) {
@@ -77153,7 +77364,7 @@ async function technicalAeoRoutes(app, opts) {
77153
77364
  }
77154
77365
 
77155
77366
  // ../api-routes/src/trends.ts
77156
- import { and as and94, asc as asc28, eq as eq111 } from "drizzle-orm";
77367
+ import { and as and95, asc as asc28, eq as eq112 } from "drizzle-orm";
77157
77368
  function parseSurface(value) {
77158
77369
  const parsed = trendSurfaceSchema.safeParse(value);
77159
77370
  if (!parsed.success) {
@@ -77209,8 +77420,8 @@ async function trendsRoutes(app) {
77209
77420
  function readSurface(app, projectId, surface) {
77210
77421
  switch (surface) {
77211
77422
  case TrendSurfaces["index-coverage"]: {
77212
- const google = app.db.select({ date: gscCoverageSnapshots.date, indexed: gscCoverageSnapshots.indexed, notIndexed: gscCoverageSnapshots.notIndexed }).from(gscCoverageSnapshots).where(eq111(gscCoverageSnapshots.projectId, projectId)).orderBy(asc28(gscCoverageSnapshots.date)).all();
77213
- const bing = app.db.select({ date: bingCoverageSnapshots.date, indexed: bingCoverageSnapshots.indexed, notIndexed: bingCoverageSnapshots.notIndexed }).from(bingCoverageSnapshots).where(eq111(bingCoverageSnapshots.projectId, projectId)).orderBy(asc28(bingCoverageSnapshots.date)).all();
77423
+ const google = app.db.select({ date: gscCoverageSnapshots.date, indexed: gscCoverageSnapshots.indexed, notIndexed: gscCoverageSnapshots.notIndexed }).from(gscCoverageSnapshots).where(eq112(gscCoverageSnapshots.projectId, projectId)).orderBy(asc28(gscCoverageSnapshots.date)).all();
77424
+ const bing = app.db.select({ date: bingCoverageSnapshots.date, indexed: bingCoverageSnapshots.indexed, notIndexed: bingCoverageSnapshots.notIndexed }).from(bingCoverageSnapshots).where(eq112(bingCoverageSnapshots.projectId, projectId)).orderBy(asc28(bingCoverageSnapshots.date)).all();
77214
77425
  const coverage = (key, label, rows) => ({
77215
77426
  key,
77216
77427
  label,
@@ -77222,7 +77433,7 @@ function readSurface(app, projectId, surface) {
77222
77433
  return [coverage("google", "Google", google), coverage("bing", "Bing", bing)];
77223
77434
  }
77224
77435
  case TrendSurfaces["site-health"]: {
77225
- const rows = app.db.select({ auditedAt: siteAuditSnapshots.auditedAt, aggregateScore: siteAuditSnapshots.aggregateScore, pagesAudited: siteAuditSnapshots.pagesAudited }).from(siteAuditSnapshots).innerJoin(runs, eq111(siteAuditSnapshots.runId, runs.id)).where(and94(...surfaceableSiteAuditFilters(projectId))).orderBy(asc28(siteAuditSnapshots.auditedAt)).all();
77436
+ const rows = app.db.select({ auditedAt: siteAuditSnapshots.auditedAt, aggregateScore: siteAuditSnapshots.aggregateScore, pagesAudited: siteAuditSnapshots.pagesAudited }).from(siteAuditSnapshots).innerJoin(runs, eq112(siteAuditSnapshots.runId, runs.id)).where(and95(...surfaceableSiteAuditFilters(projectId))).orderBy(asc28(siteAuditSnapshots.auditedAt)).all();
77226
77437
  return [{
77227
77438
  key: "site-health",
77228
77439
  label: "Site Health",
@@ -77234,7 +77445,7 @@ function readSurface(app, projectId, surface) {
77234
77445
  }
77235
77446
  case TrendSurfaces.backlinks: {
77236
77447
  const totals = computeFilteredReleaseTotals(app.db, projectId, BacklinkSources.commoncrawl);
77237
- const rows = app.db.select({ release: backlinkSummaries.release, totalLinkingDomains: backlinkSummaries.totalLinkingDomains, queriedAt: backlinkSummaries.queriedAt }).from(backlinkSummaries).where(and94(eq111(backlinkSummaries.projectId, projectId), eq111(backlinkSummaries.source, BacklinkSources.commoncrawl))).all().sort((a, b) => compareReleaseIds(a.release, b.release)).map((row) => {
77448
+ const rows = app.db.select({ release: backlinkSummaries.release, totalLinkingDomains: backlinkSummaries.totalLinkingDomains, queriedAt: backlinkSummaries.queriedAt }).from(backlinkSummaries).where(and95(eq112(backlinkSummaries.projectId, projectId), eq112(backlinkSummaries.source, BacklinkSources.commoncrawl))).all().sort((a, b) => compareReleaseIds(a.release, b.release)).map((row) => {
77238
77449
  const filtered = totals.get(row.release);
77239
77450
  const known = (filtered?.domainRows ?? 0) > 0 || row.totalLinkingDomains === 0;
77240
77451
  const windowEnd = releaseWindowEndMonth(row.release);
@@ -77264,7 +77475,7 @@ function readSurface(app, projectId, surface) {
77264
77475
  // ../api-routes/src/research.ts
77265
77476
  import crypto60 from "crypto";
77266
77477
  import { z as z7 } from "zod";
77267
- import { and as and95, count as count9, desc as desc51, eq as eq112, gte as gte33, lt as lt19, or as or22, sql as sql46 } from "drizzle-orm";
77478
+ import { and as and96, count as count9, desc as desc51, eq as eq113, gte as gte33, lt as lt20, or as or22, sql as sql46 } from "drizzle-orm";
77268
77479
  var sameLocation = (a, b) => a.label === b.label && a.city === b.city && a.region === b.region && a.country === b.country && a.timezone === b.timezone;
77269
77480
  var BATCH_RECEIPT_PREFIX = "__canonry_research_batch__:";
77270
77481
  async function researchRoutes(app, opts) {
@@ -77283,7 +77494,7 @@ async function researchRoutes(app, opts) {
77283
77494
  const input = parsed.data;
77284
77495
  if (input.idempotencyKey?.startsWith(BATCH_RECEIPT_PREFIX)) throw validationError("This idempotency key prefix is reserved for internal research batch receipts.");
77285
77496
  const requestHash2 = directResearchRequestHash(input);
77286
- const existingReceipt = input.idempotencyKey ? app.db.select().from(researchRuns).where(and95(eq112(researchRuns.projectId, project.id), eq112(researchRuns.idempotencyKey, input.idempotencyKey))).get() : void 0;
77497
+ const existingReceipt = input.idempotencyKey ? app.db.select().from(researchRuns).where(and96(eq113(researchRuns.projectId, project.id), eq113(researchRuns.idempotencyKey, input.idempotencyKey))).get() : void 0;
77287
77498
  if (existingReceipt) {
77288
77499
  assertSameDirectResearchRequest(existingReceipt, input, requestHash2);
77289
77500
  const result2 = getDetail(app, project.id, existingReceipt.id);
@@ -77318,7 +77529,7 @@ async function researchRoutes(app, opts) {
77318
77529
  const initiatedBy = researchPrincipal(request);
77319
77530
  const decision = app.db.transaction((tx) => {
77320
77531
  if (input.idempotencyKey) {
77321
- const existing = tx.select().from(researchRuns).where(and95(eq112(researchRuns.projectId, project.id), eq112(researchRuns.idempotencyKey, input.idempotencyKey))).get();
77532
+ const existing = tx.select().from(researchRuns).where(and96(eq113(researchRuns.projectId, project.id), eq113(researchRuns.idempotencyKey, input.idempotencyKey))).get();
77322
77533
  if (existing) {
77323
77534
  assertSameDirectResearchRequest(existing, input, requestHash2);
77324
77535
  return { reused: true, id: existing.id, shouldDispatch: existing.status === ResearchRunStatuses.queued };
@@ -77326,10 +77537,10 @@ async function researchRoutes(app, opts) {
77326
77537
  }
77327
77538
  if (initiatedBy?.limited) {
77328
77539
  const { start, end, date } = utcDayBounds(now);
77329
- const used = tx.select({ value: count9() }).from(researchRuns).where(and95(
77330
- eq112(researchRuns.projectId, project.id),
77540
+ const used = tx.select({ value: count9() }).from(researchRuns).where(and96(
77541
+ eq113(researchRuns.projectId, project.id),
77331
77542
  gte33(researchRuns.createdAt, start),
77332
- lt19(researchRuns.createdAt, end),
77543
+ lt20(researchRuns.createdAt, end),
77333
77544
  sql46`(json_extract(${researchRuns.initiatedBy}, '$.role') = ${UserRoles.viewer} OR json_extract(${researchRuns.initiatedBy}, '$.limited') = 1)`
77334
77545
  )).get()?.value ?? 0;
77335
77546
  if (used >= viewerDailyRunLimit) {
@@ -77358,15 +77569,15 @@ async function researchRoutes(app, opts) {
77358
77569
  if (input.idempotencyKey.startsWith(BATCH_RECEIPT_PREFIX)) throw validationError("This idempotency key prefix is reserved for internal research batch receipts.");
77359
77570
  const requestHash2 = researchBatchRequestHash(input);
77360
77571
  const receiptKeys = input.runs.map((_run, index) => batchChildReceiptKey(input.idempotencyKey, index));
77361
- const firstReceipt = app.db.select().from(researchRuns).where(and95(
77362
- eq112(researchRuns.projectId, project.id),
77363
- eq112(researchRuns.idempotencyKey, receiptKeys[0])
77572
+ const firstReceipt = app.db.select().from(researchRuns).where(and96(
77573
+ eq113(researchRuns.projectId, project.id),
77574
+ eq113(researchRuns.idempotencyKey, receiptKeys[0])
77364
77575
  )).get();
77365
77576
  if (firstReceipt) {
77366
77577
  if (firstReceipt.requestHash !== requestHash2) throw alreadyExists("Research batch idempotency key", input.idempotencyKey);
77367
- const saved = receiptKeys.map((key) => app.db.select().from(researchRuns).where(and95(
77368
- eq112(researchRuns.projectId, project.id),
77369
- eq112(researchRuns.idempotencyKey, key)
77578
+ const saved = receiptKeys.map((key) => app.db.select().from(researchRuns).where(and96(
77579
+ eq113(researchRuns.projectId, project.id),
77580
+ eq113(researchRuns.idempotencyKey, key)
77370
77581
  )).get());
77371
77582
  if (saved.some((row) => !row || row.requestHash !== requestHash2)) throw alreadyExists("Research batch idempotency key", input.idempotencyKey);
77372
77583
  const runs3 = saved.map((row) => getDetail(app, project.id, row.id));
@@ -77382,10 +77593,10 @@ async function researchRoutes(app, opts) {
77382
77593
  const decision = app.db.transaction((tx) => {
77383
77594
  if (initiatedBy?.limited) {
77384
77595
  const { start, end, date } = utcDayBounds(now);
77385
- const used = tx.select({ value: count9() }).from(researchRuns).where(and95(
77386
- eq112(researchRuns.projectId, project.id),
77596
+ const used = tx.select({ value: count9() }).from(researchRuns).where(and96(
77597
+ eq113(researchRuns.projectId, project.id),
77387
77598
  gte33(researchRuns.createdAt, start),
77388
- lt19(researchRuns.createdAt, end),
77599
+ lt20(researchRuns.createdAt, end),
77389
77600
  sql46`(json_extract(${researchRuns.initiatedBy}, '$.role') = ${UserRoles.viewer} OR json_extract(${researchRuns.initiatedBy}, '$.limited') = 1)`
77390
77601
  )).get()?.value ?? 0;
77391
77602
  if (used + prepared.length > viewerDailyRunLimit) throw researchDailyLimitExceeded(project.name, viewerDailyRunLimit, date);
@@ -77437,9 +77648,9 @@ async function researchRoutes(app, opts) {
77437
77648
  const requested = Number.parseInt(request.query.limit ?? "", 10);
77438
77649
  const limit = Number.isInteger(requested) && requested > 0 ? Math.min(requested, 100) : 20;
77439
77650
  const cursor = request.query.cursor ? parseResearchCursor(request.query.cursor, project.id) : null;
77440
- const rows = app.db.select().from(researchRuns).where(and95(
77441
- eq112(researchRuns.projectId, project.id),
77442
- cursor ? or22(lt19(researchRuns.createdAt, cursor.createdAt), and95(eq112(researchRuns.createdAt, cursor.createdAt), lt19(researchRuns.id, cursor.id))) : void 0
77651
+ const rows = app.db.select().from(researchRuns).where(and96(
77652
+ eq113(researchRuns.projectId, project.id),
77653
+ cursor ? or22(lt20(researchRuns.createdAt, cursor.createdAt), and96(eq113(researchRuns.createdAt, cursor.createdAt), lt20(researchRuns.id, cursor.id))) : void 0
77443
77654
  )).orderBy(desc51(researchRuns.createdAt), desc51(researchRuns.id)).limit(limit + 1).all();
77444
77655
  const runs2 = rows.slice(0, limit).map(serializeRun);
77445
77656
  const last = runs2.at(-1);
@@ -77589,9 +77800,9 @@ function resolveStoredResearchTemplate(stored, selection, idempotencyKey) {
77589
77800
  return stored;
77590
77801
  }
77591
77802
  function resolveResearchTemplate(app, projectId, selection, scope, location) {
77592
- const savedTemplate = app.db.select().from(measurementQueryTemplates).where(and95(
77593
- eq112(measurementQueryTemplates.projectId, projectId),
77594
- eq112(measurementQueryTemplates.id, selection.templateId)
77803
+ const savedTemplate = app.db.select().from(measurementQueryTemplates).where(and96(
77804
+ eq113(measurementQueryTemplates.projectId, projectId),
77805
+ eq113(measurementQueryTemplates.id, selection.templateId)
77595
77806
  )).get();
77596
77807
  if (!savedTemplate) throw validationError("Unknown or stale research template. Reload the selected template and try again.");
77597
77808
  const templateVersion = savedTemplate.updatedAt;
@@ -77600,7 +77811,7 @@ function resolveResearchTemplate(app, projectId, selection, scope, location) {
77600
77811
  }
77601
77812
  const bindingLocation = selection.bindingLocation === void 0 ? location : selection.bindingLocation;
77602
77813
  if (bindingLocation) {
77603
- const project = app.db.select({ locations: projects.locations }).from(projects).where(eq112(projects.id, projectId)).get();
77814
+ const project = app.db.select({ locations: projects.locations }).from(projects).where(eq113(projects.id, projectId)).get();
77604
77815
  if (!project.locations.some((item2) => sameLocation(item2, bindingLocation))) throw validationError("Research template binding location must match a configured project location.");
77605
77816
  }
77606
77817
  const { bindings, output } = expandResearchTemplate(savedTemplate, scope, bindingLocation);
@@ -77613,9 +77824,9 @@ function classifyResearchQueries(project, plan, queries2) {
77613
77824
  return queries2.map((query2) => classifier?.classify(query2) ?? null);
77614
77825
  }
77615
77826
  function getDetail(app, projectId, id) {
77616
- const row = app.db.select().from(researchRuns).where(and95(eq112(researchRuns.id, id), eq112(researchRuns.projectId, projectId))).get();
77827
+ const row = app.db.select().from(researchRuns).where(and96(eq113(researchRuns.id, id), eq113(researchRuns.projectId, projectId))).get();
77617
77828
  if (!row) throw notFound("Research run", id);
77618
- const queries2 = app.db.select().from(researchRunQueries).where(eq112(researchRunQueries.researchRunId, id)).orderBy(researchRunQueries.position).all().map(serializeQuery);
77829
+ const queries2 = app.db.select().from(researchRunQueries).where(eq113(researchRunQueries.researchRunId, id)).orderBy(researchRunQueries.position).all().map(serializeQuery);
77619
77830
  return { ...serializeRun(row), queries: queries2 };
77620
77831
  }
77621
77832
  function serializeRun(row) {
@@ -77670,7 +77881,7 @@ function utcDayBounds(now) {
77670
77881
 
77671
77882
  // ../api-routes/src/simple-measurement-definitions.ts
77672
77883
  import crypto61 from "crypto";
77673
- import { and as and96, eq as eq113 } from "drizzle-orm";
77884
+ import { and as and97, eq as eq114 } from "drizzle-orm";
77674
77885
  function captureSimpleMeasurementDefinition(db, input) {
77675
77886
  return db.transaction((tx) => {
77676
77887
  const run = tx.select({
@@ -77678,7 +77889,7 @@ function captureSimpleMeasurementDefinition(db, input) {
77678
77889
  trigger: runs.trigger,
77679
77890
  status: runs.status,
77680
77891
  measurementPlanVersionId: runs.measurementPlanVersionId
77681
- }).from(runs).where(and96(eq113(runs.projectId, input.projectId), eq113(runs.id, input.runId))).get();
77892
+ }).from(runs).where(and97(eq114(runs.projectId, input.projectId), eq114(runs.id, input.runId))).get();
77682
77893
  if (!run) throw notFound("Run", input.runId);
77683
77894
  if (run.kind !== RunKinds["answer-visibility"] || run.trigger === RunTriggers.probe || run.measurementPlanVersionId !== null) {
77684
77895
  return null;
@@ -77699,7 +77910,7 @@ function captureSimpleMeasurementDefinition(db, input) {
77699
77910
  throw validationError("Qualified aliases must be a sorted subset of the captured aliases.");
77700
77911
  }
77701
77912
  }
77702
- const existing = tx.select().from(simpleMeasurementDefinitions).where(and96(eq113(simpleMeasurementDefinitions.projectId, input.projectId), eq113(simpleMeasurementDefinitions.runId, input.runId))).get();
77913
+ const existing = tx.select().from(simpleMeasurementDefinitions).where(and97(eq114(simpleMeasurementDefinitions.projectId, input.projectId), eq114(simpleMeasurementDefinitions.runId, input.runId))).get();
77703
77914
  if (existing) {
77704
77915
  const frozen = simpleMeasurementDefinitionSchema.parse(existing.definition);
77705
77916
  const { qualifiedAliases: _liveQualifiedAliases, ...liveIdentity } = definition2.identity;
@@ -77725,15 +77936,15 @@ function captureSimpleMeasurementDefinition(db, input) {
77725
77936
  if (run.status !== RunStatuses.running) {
77726
77937
  throw validationError("Only a running run can capture a new measurement definition.");
77727
77938
  }
77728
- if (tx.select({ id: querySnapshots.id }).from(querySnapshots).where(eq113(querySnapshots.runId, input.runId)).limit(1).get()) {
77939
+ if (tx.select({ id: querySnapshots.id }).from(querySnapshots).where(eq114(querySnapshots.runId, input.runId)).limit(1).get()) {
77729
77940
  throw validationError("This run already has stored answers. Its measurement definition cannot be inferred afterward.");
77730
77941
  }
77731
- const projectQueryIds = new Set(tx.select({ id: queries.id }).from(queries).where(eq113(queries.projectId, input.projectId)).all().map((query2) => query2.id));
77942
+ const projectQueryIds = new Set(tx.select({ id: queries.id }).from(queries).where(eq114(queries.projectId, input.projectId)).all().map((query2) => query2.id));
77732
77943
  if (definition2.queries.some((query2) => !projectQueryIds.has(query2.queryId))) {
77733
77944
  throw validationError("Every captured query must belong to the run project.");
77734
77945
  }
77735
77946
  if (definition2.competitors !== void 0) {
77736
- const liveCompetitorDomains = new Set(tx.select({ domain: competitors.domain }).from(competitors).where(eq113(competitors.projectId, input.projectId)).orderBy(competitors.domain).all().map((competitor) => competitor.domain));
77947
+ const liveCompetitorDomains = new Set(tx.select({ domain: competitors.domain }).from(competitors).where(eq114(competitors.projectId, input.projectId)).orderBy(competitors.domain).all().map((competitor) => competitor.domain));
77737
77948
  const capturedDomains = new Set(definition2.competitors.map((competitor) => competitor.domain));
77738
77949
  if (liveCompetitorDomains.size !== capturedDomains.size || [...liveCompetitorDomains].some((domain) => !capturedDomains.has(domain))) {
77739
77950
  throw validationError("Captured competitors must exactly match the project competitors dispatched for this run.");