@canonry/canonry 7.86.0 → 7.87.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/assets/agent-workspace/skills/canonry/references/canonry-cli.md +21 -1
- package/assets/agent-workspace/skills/canonry/references/server-side-traffic.md +10 -6
- package/assets/assets/{AgentSetupPage-0f7Ca4IZ.js → AgentSetupPage-_ZP3F_zA.js} +4 -4
- package/assets/assets/{AuditHistoryPanel-BAQuFTNl.js → AuditHistoryPanel-9B0XpYsN.js} +1 -1
- package/assets/assets/{BacklinksPage-BK2bn9Xg.js → BacklinksPage-CsvVgXTZ.js} +1 -1
- package/assets/assets/{ChartPrimitives-B5L9FUj3.js → ChartPrimitives-4D5wgAn5.js} +1 -1
- package/assets/assets/{HistoryPage-ZBfL9nGw.js → HistoryPage-BCxpCul0.js} +1 -1
- package/assets/assets/{MeasurementPropertyPage-DDSOq4aU.js → MeasurementPropertyPage-BObvWjVx.js} +4 -4
- package/assets/assets/ProjectPage-y378tOaT.js +11 -0
- package/assets/assets/{RunsPage-ke0wMYLg.js → RunsPage-CfQ_kqlu.js} +1 -1
- package/assets/assets/{SettingsPage-Bbi-sTBk.js → SettingsPage-PIgRvqJc.js} +1 -1
- package/assets/assets/SiteHealthSection-DuUE1cB0.js +2 -0
- package/assets/assets/ToneBadge-BgTLSTKp.js +60 -0
- package/assets/assets/{TrafficPage-nXixENx9.js → TrafficPage-B_YunwjH.js} +1 -1
- package/assets/assets/TrafficSourceDetailPage-C51W87fU.js +1 -0
- package/assets/assets/{account-context-BmN431xn.js → account-context-CVjb3ZSn.js} +1 -1
- package/assets/assets/agent-trust-copy-BlpC8jo5.js +1 -0
- package/assets/assets/{card-DUTMWeHB.js → card-BnVez9nG.js} +1 -1
- package/assets/assets/{extract-error-message-BFaIHLqb.js → extract-error-message-Dqirbrl0.js} +1 -1
- package/assets/assets/{format-helpers-D1w2yznw.js → format-helpers-DVXZD864.js} +1 -1
- package/assets/assets/{index-DIHdWY7b.css → index-BOUrrC9J.css} +1 -1
- package/assets/assets/index-CsvEqhK4.js +60 -0
- package/assets/assets/query-client-CVc7wMKJ.js +1 -0
- package/assets/assets/react-query-CSlTSOjz.js +1 -0
- package/assets/assets/{react-sigma_core.esm.min-cvMDhRNS.js → react-sigma_core.esm.min-CVfGIEIE.js} +3 -3
- package/assets/assets/{service-CvCKbPnf.js → service-BGqeyqhZ.js} +1 -1
- package/assets/index.html +9 -9
- package/dist/{ads-7IJTHW6T.js → ads-ZUZWSIBV.js} +3 -3
- package/dist/{agent-4UC2X7O2.js → agent-YDS3WY7B.js} +7 -7
- package/dist/{agent-ask-SVPU2WQU.js → agent-ask-7VUPXTXL.js} +3 -3
- package/dist/{agent-conversations-PJQRY5RX.js → agent-conversations-WPEBK3BS.js} +3 -3
- package/dist/{agent-journeys-JOD5DQ6G.js → agent-journeys-H5CCYLNB.js} +3 -3
- package/dist/{agent-memory-KTMZMUYF.js → agent-memory-CNPKPIWD.js} +3 -3
- package/dist/{agent-providers-VEBB3WEW.js → agent-providers-U7FLJO5I.js} +3 -3
- package/dist/{agent-transcript-RRZJCMK2.js → agent-transcript-VY3BQNPU.js} +3 -3
- package/dist/{analytics-EZV4XJKR.js → analytics-PYUNO7C2.js} +3 -3
- package/dist/{apply-NP7ORQS4.js → apply-YNBEYC2G.js} +3 -3
- package/dist/{backfill-7T6VFPWT.js → backfill-PNHBAV5N.js} +10 -10
- package/dist/{backlinks-KTEJM2IP.js → backlinks-LVHZ2MQ7.js} +3 -3
- package/dist/{bing-TC54HJUB.js → bing-A2MUG6AV.js} +3 -3
- package/dist/{bootstrap-UDSN22NT.js → bootstrap-AZYQXBHN.js} +10 -10
- package/dist/{cdp-IZSHF3MS.js → cdp-HCYY4SXR.js} +7 -7
- package/dist/{chunk-IILPHGRC.js → chunk-2AXFKYJU.js} +2 -2
- package/dist/{chunk-U3QNTJBP.js → chunk-2FJB3CSS.js} +1 -1
- package/dist/{chunk-HHMOUS26.js → chunk-2I22IUA3.js} +5 -5
- package/dist/{chunk-KEIKD6JQ.js → chunk-3KK6FWDF.js} +1 -1
- package/dist/{chunk-STHWLBYT.js → chunk-4FWDITQG.js} +1 -1
- package/dist/{chunk-3K3QBKNC.js → chunk-4LNZTL7M.js} +3 -3
- package/dist/{chunk-Z5LJI5XK.js → chunk-6F5ULQDY.js} +4 -4
- package/dist/{chunk-OXI45LZP.js → chunk-BJHHD7VE.js} +2 -2
- package/dist/{chunk-TTXBQCE6.js → chunk-BKZ6PQAE.js} +21 -21
- package/dist/{chunk-EX6GVZAE.js → chunk-CBWGUGME.js} +2 -1
- package/dist/{chunk-LJ6VNMCY.js → chunk-CR54BCUP.js} +7 -7
- package/dist/{chunk-CBNFWSWU.js → chunk-DOPUEXMC.js} +2 -2
- package/dist/{chunk-RI2ZT3OR.js → chunk-ECZGTJIQ.js} +6 -6
- package/dist/{chunk-KUVTXIFX.js → chunk-F643WELJ.js} +1913 -1743
- package/dist/{chunk-5WEGX3H7.js → chunk-FAMCDCVK.js} +1 -1
- package/dist/{chunk-5ZAQVRFL.js → chunk-G44S34CE.js} +2 -2
- package/dist/{chunk-UTG6UUGK.js → chunk-GBWCEOZR.js} +1 -1
- package/dist/{chunk-E6IZVSRY.js → chunk-GDU6IMGB.js} +2 -2
- package/dist/{chunk-RR76O744.js → chunk-GXTCDNX7.js} +1 -1
- package/dist/{chunk-JAK37YKH.js → chunk-IBGZ6D6R.js} +2 -2
- package/dist/{chunk-WVMWY4SE.js → chunk-ICS2WJ7H.js} +3 -3
- package/dist/{chunk-2J5EUXO2.js → chunk-IK7QEKKD.js} +1 -1
- package/dist/{chunk-X3EU4UPS.js → chunk-INH773FB.js} +3 -3
- package/dist/{chunk-5KHF5FV5.js → chunk-J7ITGXCS.js} +4 -4
- package/dist/{chunk-BLGYC2K7.js → chunk-JAYUWQAV.js} +1 -1
- package/dist/{chunk-GO4RP6XE.js → chunk-JDTKG7KJ.js} +7 -3
- package/dist/{chunk-F5SQITTJ.js → chunk-MNLAIB67.js} +1 -1
- package/dist/{chunk-SUSQEH2D.js → chunk-O26FQ7RI.js} +1 -1
- package/dist/{chunk-4EKPEVJ2.js → chunk-RHOIR5QY.js} +5 -5
- package/dist/{chunk-UPFWF6TF.js → chunk-RSPOFH66.js} +4 -4
- package/dist/{chunk-VBW33U4E.js → chunk-TEQF4ZJA.js} +22 -2
- package/dist/{chunk-AZP37FMA.js → chunk-TN53B5AB.js} +28 -3
- package/dist/{chunk-ZFL5DOGJ.js → chunk-UXSWQAVZ.js} +395 -342
- package/dist/{chunk-MJE3SGFW.js → chunk-VDYXB6SV.js} +1 -1
- package/dist/{chunk-DU4QEC65.js → chunk-W7DHXSXN.js} +281 -53
- package/dist/{citations-TOZK6OFP.js → citations-3EZTHA65.js} +3 -3
- package/dist/{cli-catalog-LNWBJNOD.js → cli-catalog-CBTM3JKP.js} +3 -3
- package/dist/{cli-commands-3HX4JSDX.js → cli-commands-62QECRBV.js} +7 -7
- package/dist/cli.js +13 -13
- package/dist/{competitor-54HLL354.js → competitor-VH4TXJLT.js} +3 -3
- package/dist/{content-ZIGSMH5U.js → content-TSMNHXJZ.js} +3 -3
- package/dist/{dashboard-YJH6IQGK.js → dashboard-4HOXCUQE.js} +6 -6
- package/dist/{demo-ZYK5XCP3.js → demo-IY7Y7KR3.js} +1 -1
- package/dist/{demo-server-LZ6MWJ2S.js → demo-server-BLLSDILQ.js} +13 -12
- package/dist/{discover-22QKOFIM.js → discover-XTJXTKCI.js} +3 -3
- package/dist/{discover-eval-6IFCCH2S.js → discover-eval-IVQI5YEW.js} +3 -3
- package/dist/{doctor-WLLBFOJU.js → doctor-SJ4SHSQO.js} +3 -3
- package/dist/{evidence-7VHTWNZW.js → evidence-C6T556YH.js} +3 -3
- package/dist/{export-cmd-5RZ3QDFH.js → export-cmd-JQZ5ZRSI.js} +3 -3
- package/dist/{ga-LPI3CWY4.js → ga-W2WHVRHN.js} +3 -3
- package/dist/{gbp-YBOMWR75.js → gbp-3AZIV5WF.js} +3 -3
- package/dist/{google-N42X5FBH.js → google-3CKWPZT7.js} +4 -4
- package/dist/{google-marketing-SO4B7Z7L.js → google-marketing-PJBGQYYW.js} +1 -1
- package/dist/{health-cmd-W7RQWCEB.js → health-cmd-4JTQMCXM.js} +3 -3
- package/dist/{history-SDRTOIDZ.js → history-TXUJFEO2.js} +3 -3
- package/dist/index.js +25 -25
- package/dist/{init-DPXOGP3P.js → init-JY76QAOC.js} +12 -12
- package/dist/{insights-636LLPVG.js → insights-XZQO4B45.js} +3 -3
- package/dist/intelligence-service-GJYBEHJE.js +16 -0
- package/dist/{journeys-NIXKVYA4.js → journeys-VFIV5JYV.js} +3 -3
- package/dist/{keys-U2JR56TX.js → keys-PV23G4LW.js} +3 -3
- package/dist/{keyword-GLRK2ZHR.js → keyword-D76KBIV5.js} +3 -3
- package/dist/{logs-ENACGWGS.js → logs-DYDZ6IEJ.js} +3 -3
- package/dist/{mcp-DST2XTP2.js → mcp-GAR5LDQD.js} +4 -4
- package/dist/mcp.js +9 -9
- package/dist/{measurement-plan-Y3SA4ZQC.js → measurement-plan-K24EOGMI.js} +5 -5
- package/dist/{notify-ZYXJQUHU.js → notify-XQHMPDQQ.js} +3 -3
- package/dist/{organic-evidence-NSX7VCNW.js → organic-evidence-UHHE7AO4.js} +3 -3
- package/dist/{overview-UO6Z64IT.js → overview-AUZ3XRS5.js} +4 -4
- package/dist/{project-BHXUPHQA.js → project-FUCPU2BD.js} +3 -3
- package/dist/{query-GDRAKEN5.js → query-HRMSXDKL.js} +3 -3
- package/dist/{research-YIOQ7WVP.js → research-R23XLZJ3.js} +3 -3
- package/dist/{results-export-KXMCAEHJ.js → results-export-N6IV3KG4.js} +3 -3
- package/dist/{run-6PN5GOVE.js → run-CJLG5EXH.js} +4 -4
- package/dist/{schedule-5B67GLAM.js → schedule-IPDCQ2I5.js} +3 -3
- package/dist/{search-BMVPCN2V.js → search-ZU4AKJ76.js} +3 -3
- package/dist/{serve-HB7DI7OC.js → serve-ZQXMTDE7.js} +26 -26
- package/dist/{session-3RTDTW6Z.js → session-MGOUDLXN.js} +13 -13
- package/dist/{settings-FJEYHYRH.js → settings-4FS5NRKP.js} +3 -3
- package/dist/{skills-6E7IUQUO.js → skills-GSFHACGN.js} +2 -2
- package/dist/{snapshot-BJQ7WPJZ.js → snapshot-4NUVPDK3.js} +3 -3
- package/dist/{src-QU75IDHI.js → src-SJYGA7NH.js} +2 -2
- package/dist/{status-3GHFPN54.js → status-ASBLMFP2.js} +4 -4
- package/dist/{technical-aeo-76JRC66W.js → technical-aeo-QXHRBGYY.js} +3 -3
- package/dist/{telemetry-KB73JBDZ.js → telemetry-J2I577I3.js} +4 -4
- package/dist/{tools-6VWDHNPZ.js → tools-ZWFLE5UR.js} +11 -11
- package/dist/{traffic-XB7MZJ23.js → traffic-B7Q4WPOV.js} +5 -5
- package/dist/trends-YOLCJBNM.js +56 -0
- package/dist/{users-HYBG3J72.js → users-LPSCZV3M.js} +3 -3
- package/dist/{visibility-stats-ZDB2LMRJ.js → visibility-stats-JJQTZSSK.js} +3 -3
- package/dist/{wordpress-SXKR6XNT.js → wordpress-4IKDT5KT.js} +3 -3
- package/package.json +1 -1
- package/assets/assets/ProjectPage-8cVgOm5o.js +0 -11
- package/assets/assets/SiteHealthSection-eKxTDUE2.js +0 -2
- package/assets/assets/ToneBadge-WAMyKadz.js +0 -60
- package/assets/assets/TrafficSourceDetailPage-ir5zJU_l.js +0 -1
- package/assets/assets/agent-trust-copy-zfx8m-cx.js +0 -1
- package/assets/assets/index-C5r0VqgO.js +0 -60
- package/assets/assets/query-client-DrIawr39.js +0 -1
- package/assets/assets/react-query-DQuOVjDi.js +0 -1
- package/dist/intelligence-service-JRVESWU2.js +0 -16
|
@@ -13,14 +13,14 @@ import {
|
|
|
13
13
|
normalizeCloudflareWorkerEvent,
|
|
14
14
|
sessionizeJourneyRequests,
|
|
15
15
|
verifyRequestSignature
|
|
16
|
-
} from "./chunk-
|
|
16
|
+
} from "./chunk-VDYXB6SV.js";
|
|
17
17
|
import {
|
|
18
18
|
currentOutcomeAttribution,
|
|
19
19
|
getRequestContext,
|
|
20
20
|
registerRequestContext,
|
|
21
21
|
requestAuditIdentity,
|
|
22
22
|
sanitizeRequestContext
|
|
23
|
-
} from "./chunk-
|
|
23
|
+
} from "./chunk-GBWCEOZR.js";
|
|
24
24
|
import {
|
|
25
25
|
RawEventSampleEventTypes,
|
|
26
26
|
SentimentIdempotencyConflict,
|
|
@@ -168,7 +168,7 @@ import {
|
|
|
168
168
|
userSessions,
|
|
169
169
|
users,
|
|
170
170
|
writeNegativeReviewMaxStars
|
|
171
|
-
} from "./chunk-
|
|
171
|
+
} from "./chunk-O26FQ7RI.js";
|
|
172
172
|
import {
|
|
173
173
|
ADS_ACTIVATE_SCOPE,
|
|
174
174
|
ADS_ACTIVATION_ABSOLUTE_MAX_ENTITIES,
|
|
@@ -403,6 +403,7 @@ import {
|
|
|
403
403
|
SiteCrawlUrlVersionFilters,
|
|
404
404
|
SiteHealthLinkKinds,
|
|
405
405
|
SiteHealthTemplateDetections,
|
|
406
|
+
TREND_SURFACE_DEFAULT_WINDOW,
|
|
406
407
|
TrafficEventConfidences,
|
|
407
408
|
TrafficEventKinds,
|
|
408
409
|
TrafficEvidenceKinds,
|
|
@@ -412,6 +413,9 @@ import {
|
|
|
412
413
|
TrafficSourceAuthModes,
|
|
413
414
|
TrafficSourceStatuses,
|
|
414
415
|
TrafficSourceTypes,
|
|
416
|
+
TrendBetter,
|
|
417
|
+
TrendSurfaces,
|
|
418
|
+
TrendUnits,
|
|
415
419
|
USAGE_TELEMETRY_HEADERS,
|
|
416
420
|
UserRoles,
|
|
417
421
|
VerificationStatuses,
|
|
@@ -514,6 +518,7 @@ import {
|
|
|
514
518
|
breakdownShares,
|
|
515
519
|
bucketDuration,
|
|
516
520
|
buildHarvestAnchorTerms,
|
|
521
|
+
buildLevelTrendFigure,
|
|
517
522
|
buildMeasurementExecutionIdentity,
|
|
518
523
|
buildMeasurementRunManifestV1,
|
|
519
524
|
buildRedactedNotificationUrl,
|
|
@@ -772,6 +777,7 @@ import {
|
|
|
772
777
|
internalError,
|
|
773
778
|
intersectScopes,
|
|
774
779
|
isBrowserProvider,
|
|
780
|
+
isCalendarDate,
|
|
775
781
|
isEligibleFor,
|
|
776
782
|
isFetchTransportError,
|
|
777
783
|
isGa4AiAssistantChannel,
|
|
@@ -1203,6 +1209,9 @@ import {
|
|
|
1203
1209
|
trafficSyncResponseSchema,
|
|
1204
1210
|
trafficUnrecognizedAgentsQuerySchema,
|
|
1205
1211
|
trafficUnrecognizedAgentsResponseSchema,
|
|
1212
|
+
trendSurfaceSchema,
|
|
1213
|
+
trendWindowSpan,
|
|
1214
|
+
trendsResponseSchema,
|
|
1206
1215
|
truncateUtf16,
|
|
1207
1216
|
uiTelemetryEventSchema,
|
|
1208
1217
|
unsupportedKind,
|
|
@@ -1235,7 +1244,7 @@ import {
|
|
|
1235
1244
|
wordpressSchemaDeployResultDtoSchema,
|
|
1236
1245
|
wordpressSchemaStatusResultDtoSchema,
|
|
1237
1246
|
wordpressStatusDtoSchema
|
|
1238
|
-
} from "./chunk-
|
|
1247
|
+
} from "./chunk-F643WELJ.js";
|
|
1239
1248
|
|
|
1240
1249
|
// ../api-routes/src/user-session.ts
|
|
1241
1250
|
import crypto2 from "crypto";
|
|
@@ -6336,6 +6345,7 @@ function isTechnicalAeoRead(request, url) {
|
|
|
6336
6345
|
if (rest && /^technical-aeo\/runs\/[^/]+\/(?:progress|page-health-preview)$/.test(rest)) {
|
|
6337
6346
|
return true;
|
|
6338
6347
|
}
|
|
6348
|
+
if (rest === "trends") return queryValue(request, "surface") === "site-health";
|
|
6339
6349
|
return rest === "technical-aeo" || rest === "technical-aeo/pages" || rest === "technical-aeo/trend" || rest === "technical-aeo/crawl" || rest === "technical-aeo/graph" || rest === "technical-aeo/subgraph" || rest === "technical-aeo/path" || rest === "technical-aeo/changes" || rest === "technical-aeo/crawl/pages/audit" || rest === "technical-aeo/crawl/pages" || rest === "technical-aeo/runs" || rest === "technical-aeo/structure" || rest === "technical-aeo/internal-links" || rest === "technical-aeo/internal-links/neighbors" || rest === "technical-aeo/dead-links";
|
|
6340
6350
|
}
|
|
6341
6351
|
function isEmbedQueryWrite(request) {
|
|
@@ -31509,9 +31519,9 @@ function buildReferralAssessment(db, projectName, input) {
|
|
|
31509
31519
|
const { rows: observedRows, counts: raw } = sumCounts(sql23`1 = 1`);
|
|
31510
31520
|
const landed = sumCounts(referralLandedCondition()).counts;
|
|
31511
31521
|
const countable = sumCounts(countableReferralCondition()).counts;
|
|
31512
|
-
const
|
|
31513
|
-
const redirects =
|
|
31514
|
-
const subresources =
|
|
31522
|
+
const difference3 = (whole, part) => aiReferralClassCounts(whole.total - part.total, whole.paid - part.paid, whole.organic - part.organic);
|
|
31523
|
+
const redirects = difference3(raw, landed);
|
|
31524
|
+
const subresources = difference3(landed, countable);
|
|
31515
31525
|
const candidates = db.select({
|
|
31516
31526
|
sourceId: aiReferralEventsHourly.sourceId,
|
|
31517
31527
|
product: aiReferralEventsHourly.product,
|
|
@@ -35736,6 +35746,7 @@ var SCHEMA_TABLE = {
|
|
|
35736
35746
|
SiteAuditRunResponseDto: siteAuditRunResponseSchema,
|
|
35737
35747
|
SiteAuditScoreDto: siteAuditScoreSchema,
|
|
35738
35748
|
SiteAuditTrendResponseDto: siteAuditTrendResponseSchema,
|
|
35749
|
+
TrendsResponseDto: trendsResponseSchema,
|
|
35739
35750
|
SiteCrawlDeadLinksResponseDto: siteCrawlDeadLinksResponseSchema,
|
|
35740
35751
|
SiteCrawlGraphResponseDto: siteCrawlGraphResponseSchema,
|
|
35741
35752
|
SiteCrawlInternalLinksResponseDto: siteCrawlInternalLinksResponseSchema,
|
|
@@ -43673,6 +43684,25 @@ var routeCatalog = [
|
|
|
43673
43684
|
404: errorResponse("Project not found.")
|
|
43674
43685
|
}
|
|
43675
43686
|
},
|
|
43687
|
+
{
|
|
43688
|
+
method: "get",
|
|
43689
|
+
path: "/api/v1/projects/{name}/trends",
|
|
43690
|
+
summary: "Get a dashboard trend card: figures, their readings over a window, and each change",
|
|
43691
|
+
description: "Stored readings only; nothing calls a provider. `index-coverage` returns Google and Bing indexed and not-indexed pages, `site-health` the site score and pages checked per scan, `backlinks` referring domains, total linking hosts and top-10 concentration per Common Crawl release, with crawler and proxy hosts left out as on the Backlinks tab. Every figure is a level: its value is the latest reading in the window and its change runs from the first reading in the window (`change.basis`). The window ends on the surface's latest reading; pass `startDate`/`endDate` instead to read your own calendar range. A Common Crawl release reads at its window's last month, in crawl order, and carries its window as `label`. Readings before a figure's `recordedFrom` were never recorded. Groups never recorded (for example Bing not connected) are left out.",
|
|
43692
|
+
tags: ["trends"],
|
|
43693
|
+
parameters: [
|
|
43694
|
+
nameParameter,
|
|
43695
|
+
{ name: "surface", in: "query", required: true, description: "Which card to read.", schema: { type: "string", enum: ["index-coverage", "site-health", "backlinks"] } },
|
|
43696
|
+
{ name: "window", in: "query", description: "Window ending on the latest reading. Defaults to 90d for index-coverage and all for site-health and backlinks. Any other value is rejected with 400. Not allowed with startDate or endDate.", schema: { type: "string", enum: ["7d", "30d", "90d", "all"] } },
|
|
43697
|
+
{ name: "startDate", in: "query", description: "First calendar day (YYYY-MM-DD) of your own range instead of a window. Left out, the range starts at the earliest reading.", schema: { type: "string", format: "date" } },
|
|
43698
|
+
{ name: "endDate", in: "query", description: "Last calendar day (YYYY-MM-DD) of your own range instead of a window. Left out, the range ends at the latest reading.", schema: { type: "string", format: "date" } }
|
|
43699
|
+
],
|
|
43700
|
+
responses: {
|
|
43701
|
+
200: jsonResponse("Trend card returned. `window` is null for a date range.", "TrendsResponseDto"),
|
|
43702
|
+
400: errorResponse("Missing or unknown surface, an unknown window, an invalid date, a start after the end, or a window and a date range together."),
|
|
43703
|
+
404: errorResponse("Project not found.")
|
|
43704
|
+
}
|
|
43705
|
+
},
|
|
43676
43706
|
{
|
|
43677
43707
|
method: "get",
|
|
43678
43708
|
path: "/api/v1/projects/{name}/technical-aeo/crawl",
|
|
@@ -47103,9 +47133,22 @@ function computeGscPeriodComparison(daily, bounds) {
|
|
|
47103
47133
|
impressions: comparable ? relativeChange(trailing.impressions, prior.impressions) : null,
|
|
47104
47134
|
ctr: comparable ? relativeChange(trailing.ctr, prior.ctr) : null,
|
|
47105
47135
|
position: comparable ? relativeChange(trailing.position, prior.position) : null
|
|
47136
|
+
},
|
|
47137
|
+
difference: {
|
|
47138
|
+
clicks: comparable ? trailing.clicks - prior.clicks : null,
|
|
47139
|
+
impressions: comparable ? trailing.impressions - prior.impressions : null,
|
|
47140
|
+
// A rate's difference is itself a fraction, so it goes out at wire precision.
|
|
47141
|
+
ctr: comparable ? roundedFraction(difference2(trailing.ctr, prior.ctr)) : null,
|
|
47142
|
+
position: comparable ? difference2(trailing.position, prior.position) : null
|
|
47106
47143
|
}
|
|
47107
47144
|
};
|
|
47108
47145
|
}
|
|
47146
|
+
function difference2(trailing, prior) {
|
|
47147
|
+
return trailing === null || prior === null ? null : trailing - prior;
|
|
47148
|
+
}
|
|
47149
|
+
function roundedFraction(value) {
|
|
47150
|
+
return value === null ? null : roundRatio(value, RatioUnits.fraction);
|
|
47151
|
+
}
|
|
47109
47152
|
|
|
47110
47153
|
// ../api-routes/src/ga-property-data.ts
|
|
47111
47154
|
import { eq as eq73 } from "drizzle-orm";
|
|
@@ -61697,6 +61740,35 @@ function isValidReleaseId(id) {
|
|
|
61697
61740
|
function formatReleaseId(year, window2) {
|
|
61698
61741
|
return `cc-main-${year}-${window2}`;
|
|
61699
61742
|
}
|
|
61743
|
+
var MONTH_NAMES = ["jan", "feb", "mar", "apr", "may", "jun", "jul", "aug", "sep", "oct", "nov", "dec"];
|
|
61744
|
+
var MONTH_LABELS = ["Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"];
|
|
61745
|
+
function releaseWindow(id) {
|
|
61746
|
+
const match = RELEASE_ID_REGEX.exec(id);
|
|
61747
|
+
if (!match) return null;
|
|
61748
|
+
const first = MONTH_NAMES.indexOf(match[2].slice(0, 3));
|
|
61749
|
+
return { first: Number(match[1]) * 12 + first, last: Number(match[1]) * 12 + first + 2 };
|
|
61750
|
+
}
|
|
61751
|
+
var monthLabel = (absolute) => `${MONTH_LABELS[absolute % 12]} ${Math.floor(absolute / 12)}`;
|
|
61752
|
+
function compareReleaseIds(a, b) {
|
|
61753
|
+
const left = releaseWindow(a);
|
|
61754
|
+
const right = releaseWindow(b);
|
|
61755
|
+
if (left && right) return left.first - right.first;
|
|
61756
|
+
if (left) return 1;
|
|
61757
|
+
if (right) return -1;
|
|
61758
|
+
return a < b ? -1 : a > b ? 1 : 0;
|
|
61759
|
+
}
|
|
61760
|
+
function releaseWindowEndMonth(id) {
|
|
61761
|
+
const window2 = releaseWindow(id);
|
|
61762
|
+
if (!window2) return null;
|
|
61763
|
+
return `${Math.floor(window2.last / 12)}-${String(window2.last % 12 + 1).padStart(2, "0")}-01`;
|
|
61764
|
+
}
|
|
61765
|
+
function formatReleaseWindow(id) {
|
|
61766
|
+
const window2 = releaseWindow(id);
|
|
61767
|
+
if (!window2) return null;
|
|
61768
|
+
const firstYear = Math.floor(window2.first / 12);
|
|
61769
|
+
const lastYear = Math.floor(window2.last / 12);
|
|
61770
|
+
return firstYear === lastYear ? `${MONTH_LABELS[window2.first % 12]}-${monthLabel(window2.last)}` : `${monthLabel(window2.first)}-${monthLabel(window2.last)}`;
|
|
61771
|
+
}
|
|
61700
61772
|
|
|
61701
61773
|
// ../integration-commoncrawl/src/release-discovery.ts
|
|
61702
61774
|
var MONTHS = ["jan", "feb", "mar", "apr", "may", "jun", "jul", "aug", "sep", "oct", "nov", "dec"];
|
|
@@ -62193,7 +62265,12 @@ function latestSummaryForProject(db, projectId, source, release) {
|
|
|
62193
62265
|
eq84(backlinkSummaries.source, source)
|
|
62194
62266
|
];
|
|
62195
62267
|
if (release) conditions.push(eq84(backlinkSummaries.release, release));
|
|
62196
|
-
|
|
62268
|
+
const rows = db.select().from(backlinkSummaries).where(and73(...conditions)).orderBy(desc38(backlinkSummaries.queriedAt)).all();
|
|
62269
|
+
return rows.reduce((latest, row) => {
|
|
62270
|
+
if (!latest) return row;
|
|
62271
|
+
const newer = isValidReleaseId(row.release) && isValidReleaseId(latest.release) && compareReleaseIds(row.release, latest.release) > 0;
|
|
62272
|
+
return newer ? row : latest;
|
|
62273
|
+
}, void 0);
|
|
62197
62274
|
}
|
|
62198
62275
|
function parseExcludeCrawlers(value) {
|
|
62199
62276
|
if (!value) return false;
|
|
@@ -62235,6 +62312,31 @@ function computeFilteredSummary(db, base) {
|
|
|
62235
62312
|
excludedHosts: Math.max(0, unfilteredHosts - totalHosts)
|
|
62236
62313
|
};
|
|
62237
62314
|
}
|
|
62315
|
+
function computeFilteredReleaseTotals(db, projectId, source) {
|
|
62316
|
+
const kept = sql35`case when ${backlinkCrawlerExclusionClause()} then 1 else 0 end`;
|
|
62317
|
+
const rows = db.all(sql35`
|
|
62318
|
+
SELECT release,
|
|
62319
|
+
COUNT(*) AS domain_rows,
|
|
62320
|
+
SUM(kept) AS linking_domains,
|
|
62321
|
+
COALESCE(SUM(CASE WHEN kept = 1 THEN num_hosts ELSE 0 END), 0) AS hosts,
|
|
62322
|
+
COALESCE(SUM(CASE WHEN kept = 1 AND kept_rank <= 10 THEN num_hosts ELSE 0 END), 0) AS top10
|
|
62323
|
+
FROM (
|
|
62324
|
+
SELECT ${backlinkDomains.release} AS release, ${backlinkDomains.numHosts} AS num_hosts, ${kept} AS kept,
|
|
62325
|
+
ROW_NUMBER() OVER (PARTITION BY ${backlinkDomains.release}, ${kept} ORDER BY ${backlinkDomains.numHosts} DESC) AS kept_rank
|
|
62326
|
+
FROM ${backlinkDomains}
|
|
62327
|
+
WHERE ${and73(eq84(backlinkDomains.projectId, projectId), eq84(backlinkDomains.source, source))}
|
|
62328
|
+
)
|
|
62329
|
+
GROUP BY release`);
|
|
62330
|
+
return new Map(rows.map((row) => {
|
|
62331
|
+
const hosts = Number(row.hosts);
|
|
62332
|
+
return [row.release, {
|
|
62333
|
+
domainRows: Number(row.domain_rows),
|
|
62334
|
+
linkingDomains: Number(row.linking_domains),
|
|
62335
|
+
hosts,
|
|
62336
|
+
top10HostsShare: Number((hosts > 0 ? Number(row.top10) / hosts : 0).toFixed(6))
|
|
62337
|
+
}];
|
|
62338
|
+
}));
|
|
62339
|
+
}
|
|
62238
62340
|
function buildSourceAvailability(db, projectId, source, connected2, excludeCrawlers) {
|
|
62239
62341
|
const summary3 = db.select().from(backlinkSummaries).where(and73(eq84(backlinkSummaries.projectId, projectId), eq84(backlinkSummaries.source, source))).orderBy(desc38(backlinkSummaries.queriedAt)).limit(1).get();
|
|
62240
62342
|
let totalLinkingDomains = summary3?.totalLinkingDomains ?? 0;
|
|
@@ -62485,6 +62587,7 @@ var DEFAULT_VERCEL_SYNC_DEADLINE_MS = 4 * 6e4;
|
|
|
62485
62587
|
var CLOUDFLARE_QUEUE_BATCH_SIZE = 100;
|
|
62486
62588
|
var DEFAULT_CLOUDFLARE_QUEUE_SYNC_DEADLINE_MS = 4 * 6e4;
|
|
62487
62589
|
var DEFAULT_CLOUDFLARE_QUEUE_MAX_BATCHES = 300;
|
|
62590
|
+
var DEFAULT_CLOUDFLARE_QUEUE_EMPTY_PULL_BACKOFF_MS = [500, 1e3, 2e3, 4e3, 8e3];
|
|
62488
62591
|
var CLOUDFLARE_QUEUE_RESIDUAL_WARN_COUNT = 1e3;
|
|
62489
62592
|
var MIN_VERCEL_SYNC_DEADLINE_MS = 3e4;
|
|
62490
62593
|
var MAX_VERCEL_SYNC_DEADLINE_MS = 15 * 6e4;
|
|
@@ -64714,9 +64817,13 @@ async function trafficRoutes(app, opts) {
|
|
|
64714
64817
|
const readQueueRetentionSeconds = opts.getCloudflareQueueRetentionSeconds ?? getCloudflareQueueRetentionSeconds;
|
|
64715
64818
|
const cloudflareQueueMaxBatches = opts.cloudflareQueueMaxBatches ?? DEFAULT_CLOUDFLARE_QUEUE_MAX_BATCHES;
|
|
64716
64819
|
const cloudflareQueueSyncDeadlineMs = opts.cloudflareQueueSyncDeadlineMs ?? DEFAULT_CLOUDFLARE_QUEUE_SYNC_DEADLINE_MS;
|
|
64820
|
+
const cloudflareQueueEmptyPullBackoffMs = opts.cloudflareQueueEmptyPullBackoffMs ?? DEFAULT_CLOUDFLARE_QUEUE_EMPTY_PULL_BACKOFF_MS;
|
|
64717
64821
|
if (!Number.isInteger(cloudflareQueueMaxBatches) || cloudflareQueueMaxBatches < 1 || cloudflareQueueMaxBatches > 1e3) {
|
|
64718
64822
|
throw new RangeError("cloudflareQueueMaxBatches must be an integer from 1 to 1000");
|
|
64719
64823
|
}
|
|
64824
|
+
if (cloudflareQueueEmptyPullBackoffMs.some((ms) => !Number.isFinite(ms) || ms < 0)) {
|
|
64825
|
+
throw new RangeError("cloudflareQueueEmptyPullBackoffMs must hold non-negative milliseconds");
|
|
64826
|
+
}
|
|
64720
64827
|
const allowLoopback = opts.allowLoopbackWebhooks === true;
|
|
64721
64828
|
let rawSampleRetentionTimer;
|
|
64722
64829
|
app.addHook("onReady", async () => {
|
|
@@ -65898,9 +66005,20 @@ async function trafficRoutes(app, opts) {
|
|
|
65898
66005
|
break;
|
|
65899
66006
|
}
|
|
65900
66007
|
consecutiveEmptyPulls = pulled.skippedUnleasedMessageCount === 0 ? consecutiveEmptyPulls + 1 : 0;
|
|
65901
|
-
if (consecutiveEmptyPulls
|
|
65902
|
-
|
|
65903
|
-
|
|
66008
|
+
if (consecutiveEmptyPulls > 0) {
|
|
66009
|
+
const waits = remainingBacklogCount >= CLOUDFLARE_QUEUE_BATCH_SIZE ? cloudflareQueueEmptyPullBackoffMs : [0];
|
|
66010
|
+
if (consecutiveEmptyPulls > waits.length) {
|
|
66011
|
+
stopReason = CloudflareQueueDrainStopReasons["empty-pulls"];
|
|
66012
|
+
break;
|
|
66013
|
+
}
|
|
66014
|
+
const waitMs = waits[consecutiveEmptyPulls - 1];
|
|
66015
|
+
if (waitMs > 0) {
|
|
66016
|
+
if (waitMs >= drainDeadlineMs - Date.now()) {
|
|
66017
|
+
stopReason = CloudflareQueueDrainStopReasons.deadline;
|
|
66018
|
+
break;
|
|
66019
|
+
}
|
|
66020
|
+
await new Promise((resolve) => setTimeout(resolve, waitMs));
|
|
66021
|
+
}
|
|
65904
66022
|
}
|
|
65905
66023
|
} else {
|
|
65906
66024
|
consecutiveEmptyPulls = 0;
|
|
@@ -73303,6 +73421,15 @@ var FETCHED_SITE_CRAWL_STATES = /* @__PURE__ */ new Set([
|
|
|
73303
73421
|
"fetched"
|
|
73304
73422
|
]);
|
|
73305
73423
|
var SURFACEABLE_STATUSES = [RunStatuses.completed, RunStatuses.partial];
|
|
73424
|
+
function surfaceableSiteAuditFilters(projectId) {
|
|
73425
|
+
return [
|
|
73426
|
+
eq106(siteAuditSnapshots.projectId, projectId),
|
|
73427
|
+
eq106(runs.projectId, projectId),
|
|
73428
|
+
eq106(runs.kind, RunKinds["site-audit"]),
|
|
73429
|
+
inArray47(runs.status, SURFACEABLE_STATUSES),
|
|
73430
|
+
notProbeRun()
|
|
73431
|
+
];
|
|
73432
|
+
}
|
|
73306
73433
|
function siteAuditRunPhase(status, attempt, layoutState) {
|
|
73307
73434
|
switch (status) {
|
|
73308
73435
|
case RunStatuses.queued:
|
|
@@ -73714,11 +73841,7 @@ async function technicalAeoRoutes(app, opts) {
|
|
|
73714
73841
|
};
|
|
73715
73842
|
const resolveAudit = (projectId, runId, recordedBefore) => {
|
|
73716
73843
|
const filters = [
|
|
73717
|
-
|
|
73718
|
-
eq106(runs.projectId, projectId),
|
|
73719
|
-
eq106(runs.kind, RunKinds["site-audit"]),
|
|
73720
|
-
inArray47(runs.status, SURFACEABLE_STATUSES),
|
|
73721
|
-
notProbeRun(),
|
|
73844
|
+
...surfaceableSiteAuditFilters(projectId),
|
|
73722
73845
|
...recordedBefore ? [lt16(siteAuditSnapshots.createdAt, recordedBefore)] : []
|
|
73723
73846
|
];
|
|
73724
73847
|
const complete = sql41`case when ${runs.status} = ${RunStatuses.completed} then 1 else 0 end`;
|
|
@@ -74006,13 +74129,7 @@ async function technicalAeoRoutes(app, opts) {
|
|
|
74006
74129
|
auditedAt: siteAuditSnapshots.auditedAt,
|
|
74007
74130
|
aggregateScore: siteAuditSnapshots.aggregateScore,
|
|
74008
74131
|
pagesAudited: siteAuditSnapshots.pagesAudited
|
|
74009
|
-
}).from(siteAuditSnapshots).innerJoin(runs, eq106(siteAuditSnapshots.runId, runs.id)).where(and89(
|
|
74010
|
-
eq106(siteAuditSnapshots.projectId, project.id),
|
|
74011
|
-
eq106(runs.projectId, project.id),
|
|
74012
|
-
eq106(runs.kind, RunKinds["site-audit"]),
|
|
74013
|
-
inArray47(runs.status, SURFACEABLE_STATUSES),
|
|
74014
|
-
notProbeRun()
|
|
74015
|
-
)).orderBy(desc48(siteAuditSnapshots.createdAt)).limit(limit).all();
|
|
74132
|
+
}).from(siteAuditSnapshots).innerJoin(runs, eq106(siteAuditSnapshots.runId, runs.id)).where(and89(...surfaceableSiteAuditFilters(project.id))).orderBy(desc48(siteAuditSnapshots.createdAt)).limit(limit).all();
|
|
74016
74133
|
return { project: project.name, points: rows.reverse() };
|
|
74017
74134
|
});
|
|
74018
74135
|
app.get("/projects/:name/technical-aeo/crawl", async (request) => {
|
|
@@ -74748,8 +74865,8 @@ async function technicalAeoRoutes(app, opts) {
|
|
|
74748
74865
|
const internalPreviousJoin = entity === "link" ? sql41`AND previous.internal = 1` : sql41``;
|
|
74749
74866
|
const internalPrevious = entity === "link" ? sql41`AND previous.internal = 1` : sql41``;
|
|
74750
74867
|
const internalCurrentJoin = entity === "link" ? sql41`AND current.internal = 1` : sql41``;
|
|
74751
|
-
const
|
|
74752
|
-
const afterKind = changeFilter === "added" ? sql41`previous.id IS NULL` : changeFilter === "changed" ? sql41`previous.id IS NOT NULL AND ${
|
|
74868
|
+
const difference3 = entity === "page" ? PAGE_CHANGE_PREDICATE : LINK_CHANGE_PREDICATE;
|
|
74869
|
+
const afterKind = changeFilter === "added" ? sql41`previous.id IS NULL` : changeFilter === "changed" ? sql41`previous.id IS NOT NULL AND ${difference3}` : sql41`previous.id IS NULL OR (previous.id IS NOT NULL AND ${difference3})`;
|
|
74753
74870
|
const afterRows = includeAfter ? app.db.all(sql41`
|
|
74754
74871
|
SELECT ${entity} AS entity,
|
|
74755
74872
|
CASE WHEN previous.id IS NULL THEN 'added' ELSE 'changed' END AS change,
|
|
@@ -75553,10 +75670,119 @@ async function technicalAeoRoutes(app, opts) {
|
|
|
75553
75670
|
});
|
|
75554
75671
|
}
|
|
75555
75672
|
|
|
75673
|
+
// ../api-routes/src/trends.ts
|
|
75674
|
+
import { and as and90, asc as asc25, eq as eq107 } from "drizzle-orm";
|
|
75675
|
+
function parseSurface(value) {
|
|
75676
|
+
const parsed = trendSurfaceSchema.safeParse(value);
|
|
75677
|
+
if (!parsed.success) {
|
|
75678
|
+
throw validationError(`Invalid surface "${value ?? ""}". Must be one of: ${trendSurfaceSchema.options.join(", ")}.`, {
|
|
75679
|
+
field: "surface",
|
|
75680
|
+
value: value ?? null,
|
|
75681
|
+
allowed: [...trendSurfaceSchema.options]
|
|
75682
|
+
});
|
|
75683
|
+
}
|
|
75684
|
+
return parsed.data;
|
|
75685
|
+
}
|
|
75686
|
+
function parseDate(field, value) {
|
|
75687
|
+
if (value === void 0) return void 0;
|
|
75688
|
+
if (!isCalendarDate(value)) throw validationError(`Invalid ${field} "${value}". Use a calendar date, YYYY-MM-DD.`, { field, value });
|
|
75689
|
+
return value;
|
|
75690
|
+
}
|
|
75691
|
+
function parseSelection2(query, surface) {
|
|
75692
|
+
const startDate = parseDate("startDate", query.startDate);
|
|
75693
|
+
const endDate = parseDate("endDate", query.endDate);
|
|
75694
|
+
if (startDate === void 0 && endDate === void 0) {
|
|
75695
|
+
return parseWindowIn(query.window, WINDOW_SETS.metrics, { fallback: TREND_SURFACE_DEFAULT_WINDOW[surface] });
|
|
75696
|
+
}
|
|
75697
|
+
if (query.window !== void 0) {
|
|
75698
|
+
throw validationError("Pass window or a startDate/endDate range, not both.", { field: "window", value: query.window });
|
|
75699
|
+
}
|
|
75700
|
+
if (startDate && endDate && startDate > endDate) {
|
|
75701
|
+
throw validationError(`startDate ${startDate} is after endDate ${endDate}.`, { field: "startDate", value: startDate });
|
|
75702
|
+
}
|
|
75703
|
+
return { ...startDate ? { startDate } : {}, ...endDate ? { endDate } : {} };
|
|
75704
|
+
}
|
|
75705
|
+
async function trendsRoutes(app) {
|
|
75706
|
+
app.get("/projects/:name/trends", async (request) => {
|
|
75707
|
+
const project = resolveProject(app.db, request.params.name);
|
|
75708
|
+
const surface = parseSurface(request.query.surface);
|
|
75709
|
+
const selection = parseSelection2(request.query, surface);
|
|
75710
|
+
const groups = readSurface(app, project.id, surface).filter((group) => group.figures.some((figure) => figure.readings.some((reading) => reading.value !== null)));
|
|
75711
|
+
const span = trendWindowSpan(groups.flatMap((group) => group.figures.flatMap((figure) => figure.readings.filter((reading) => reading.value !== null).map((reading) => reading.date))), selection);
|
|
75712
|
+
const built = groups.map((group) => ({
|
|
75713
|
+
key: group.key,
|
|
75714
|
+
label: group.label,
|
|
75715
|
+
figures: group.figures.map((figure) => buildLevelTrendFigure({ ...figure, span }))
|
|
75716
|
+
}));
|
|
75717
|
+
return {
|
|
75718
|
+
project: project.name,
|
|
75719
|
+
surface,
|
|
75720
|
+
window: typeof selection === "string" ? selection : null,
|
|
75721
|
+
startDate: span?.startDate ?? null,
|
|
75722
|
+
endDate: span?.endDate ?? null,
|
|
75723
|
+
groups: built
|
|
75724
|
+
};
|
|
75725
|
+
});
|
|
75726
|
+
}
|
|
75727
|
+
function readSurface(app, projectId, surface) {
|
|
75728
|
+
switch (surface) {
|
|
75729
|
+
case TrendSurfaces["index-coverage"]: {
|
|
75730
|
+
const google = app.db.select({ date: gscCoverageSnapshots.date, indexed: gscCoverageSnapshots.indexed, notIndexed: gscCoverageSnapshots.notIndexed }).from(gscCoverageSnapshots).where(eq107(gscCoverageSnapshots.projectId, projectId)).orderBy(asc25(gscCoverageSnapshots.date)).all();
|
|
75731
|
+
const bing = app.db.select({ date: bingCoverageSnapshots.date, indexed: bingCoverageSnapshots.indexed, notIndexed: bingCoverageSnapshots.notIndexed }).from(bingCoverageSnapshots).where(eq107(bingCoverageSnapshots.projectId, projectId)).orderBy(asc25(bingCoverageSnapshots.date)).all();
|
|
75732
|
+
const coverage = (key, label, rows) => ({
|
|
75733
|
+
key,
|
|
75734
|
+
label,
|
|
75735
|
+
figures: [
|
|
75736
|
+
{ key: "indexed", label: "Indexed", unit: TrendUnits.count, better: TrendBetter.higher, readings: rows.map((row) => ({ date: row.date, value: row.indexed })) },
|
|
75737
|
+
{ key: "not-indexed", label: "Not indexed", unit: TrendUnits.count, better: TrendBetter.lower, readings: rows.map((row) => ({ date: row.date, value: row.notIndexed })) }
|
|
75738
|
+
]
|
|
75739
|
+
});
|
|
75740
|
+
return [coverage("google", "Google", google), coverage("bing", "Bing", bing)];
|
|
75741
|
+
}
|
|
75742
|
+
case TrendSurfaces["site-health"]: {
|
|
75743
|
+
const rows = app.db.select({ auditedAt: siteAuditSnapshots.auditedAt, aggregateScore: siteAuditSnapshots.aggregateScore, pagesAudited: siteAuditSnapshots.pagesAudited }).from(siteAuditSnapshots).innerJoin(runs, eq107(siteAuditSnapshots.runId, runs.id)).where(and90(...surfaceableSiteAuditFilters(projectId))).orderBy(asc25(siteAuditSnapshots.auditedAt)).all();
|
|
75744
|
+
return [{
|
|
75745
|
+
key: "site-health",
|
|
75746
|
+
label: "Site Health",
|
|
75747
|
+
figures: [
|
|
75748
|
+
{ key: "score", label: "Site score", unit: TrendUnits.score, better: TrendBetter.higher, readings: rows.map((row) => ({ date: row.auditedAt, observedAt: row.auditedAt, value: row.aggregateScore })) },
|
|
75749
|
+
{ key: "pages-checked", label: "Pages checked", unit: TrendUnits.count, better: TrendBetter.neither, readings: rows.map((row) => ({ date: row.auditedAt, observedAt: row.auditedAt, value: row.pagesAudited })) }
|
|
75750
|
+
]
|
|
75751
|
+
}];
|
|
75752
|
+
}
|
|
75753
|
+
case TrendSurfaces.backlinks: {
|
|
75754
|
+
const totals = computeFilteredReleaseTotals(app.db, projectId, BacklinkSources.commoncrawl);
|
|
75755
|
+
const rows = app.db.select({ release: backlinkSummaries.release, totalLinkingDomains: backlinkSummaries.totalLinkingDomains, queriedAt: backlinkSummaries.queriedAt }).from(backlinkSummaries).where(and90(eq107(backlinkSummaries.projectId, projectId), eq107(backlinkSummaries.source, BacklinkSources.commoncrawl))).all().sort((a, b) => compareReleaseIds(a.release, b.release)).map((row) => {
|
|
75756
|
+
const filtered = totals.get(row.release);
|
|
75757
|
+
const known = (filtered?.domainRows ?? 0) > 0 || row.totalLinkingDomains === 0;
|
|
75758
|
+
const windowEnd = releaseWindowEndMonth(row.release);
|
|
75759
|
+
return {
|
|
75760
|
+
date: windowEnd ?? row.queriedAt,
|
|
75761
|
+
observedAt: windowEnd ? null : row.queriedAt,
|
|
75762
|
+
label: formatReleaseWindow(row.release) ?? row.release,
|
|
75763
|
+
linkingDomains: known ? filtered?.linkingDomains ?? 0 : null,
|
|
75764
|
+
hosts: known ? filtered?.hosts ?? 0 : null,
|
|
75765
|
+
top10HostsShare: known ? filtered?.top10HostsShare ?? 0 : null
|
|
75766
|
+
};
|
|
75767
|
+
});
|
|
75768
|
+
const readings = (value) => rows.map((row) => ({ date: row.date, observedAt: row.observedAt, label: row.label, value: value(row) }));
|
|
75769
|
+
return [{
|
|
75770
|
+
key: "backlinks",
|
|
75771
|
+
label: "Backlinks",
|
|
75772
|
+
figures: [
|
|
75773
|
+
{ key: "referring-domains", label: "Referring domains", unit: TrendUnits.count, better: TrendBetter.higher, readings: readings((row) => row.linkingDomains) },
|
|
75774
|
+
{ key: "hosts", label: "Total linking hosts", unit: TrendUnits.count, better: TrendBetter.higher, readings: readings((row) => row.hosts) },
|
|
75775
|
+
{ key: "top-10-concentration", label: "Top-10 concentration", unit: TrendUnits.fraction, better: TrendBetter.neither, readings: readings((row) => row.top10HostsShare) }
|
|
75776
|
+
]
|
|
75777
|
+
}];
|
|
75778
|
+
}
|
|
75779
|
+
}
|
|
75780
|
+
}
|
|
75781
|
+
|
|
75556
75782
|
// ../api-routes/src/research.ts
|
|
75557
75783
|
import crypto59 from "crypto";
|
|
75558
75784
|
import { z as z7 } from "zod";
|
|
75559
|
-
import { and as
|
|
75785
|
+
import { and as and91, count as count9, desc as desc49, eq as eq108, gte as gte31, lt as lt17, or as or22, sql as sql42 } from "drizzle-orm";
|
|
75560
75786
|
var sameLocation = (a, b) => a.label === b.label && a.city === b.city && a.region === b.region && a.country === b.country && a.timezone === b.timezone;
|
|
75561
75787
|
var BATCH_RECEIPT_PREFIX = "__canonry_research_batch__:";
|
|
75562
75788
|
async function researchRoutes(app, opts) {
|
|
@@ -75575,7 +75801,7 @@ async function researchRoutes(app, opts) {
|
|
|
75575
75801
|
const input = parsed.data;
|
|
75576
75802
|
if (input.idempotencyKey?.startsWith(BATCH_RECEIPT_PREFIX)) throw validationError("This idempotency key prefix is reserved for internal research batch receipts.");
|
|
75577
75803
|
const requestHash2 = directResearchRequestHash(input);
|
|
75578
|
-
const existingReceipt = input.idempotencyKey ? app.db.select().from(researchRuns).where(
|
|
75804
|
+
const existingReceipt = input.idempotencyKey ? app.db.select().from(researchRuns).where(and91(eq108(researchRuns.projectId, project.id), eq108(researchRuns.idempotencyKey, input.idempotencyKey))).get() : void 0;
|
|
75579
75805
|
if (existingReceipt) {
|
|
75580
75806
|
assertSameDirectResearchRequest(existingReceipt, input, requestHash2);
|
|
75581
75807
|
const result2 = getDetail(app, project.id, existingReceipt.id);
|
|
@@ -75610,7 +75836,7 @@ async function researchRoutes(app, opts) {
|
|
|
75610
75836
|
const initiatedBy = researchPrincipal(request);
|
|
75611
75837
|
const decision = app.db.transaction((tx) => {
|
|
75612
75838
|
if (input.idempotencyKey) {
|
|
75613
|
-
const existing = tx.select().from(researchRuns).where(
|
|
75839
|
+
const existing = tx.select().from(researchRuns).where(and91(eq108(researchRuns.projectId, project.id), eq108(researchRuns.idempotencyKey, input.idempotencyKey))).get();
|
|
75614
75840
|
if (existing) {
|
|
75615
75841
|
assertSameDirectResearchRequest(existing, input, requestHash2);
|
|
75616
75842
|
return { reused: true, id: existing.id, shouldDispatch: existing.status === ResearchRunStatuses.queued };
|
|
@@ -75618,8 +75844,8 @@ async function researchRoutes(app, opts) {
|
|
|
75618
75844
|
}
|
|
75619
75845
|
if (initiatedBy?.limited) {
|
|
75620
75846
|
const { start, end, date } = utcDayBounds(now);
|
|
75621
|
-
const used = tx.select({ value: count9() }).from(researchRuns).where(
|
|
75622
|
-
|
|
75847
|
+
const used = tx.select({ value: count9() }).from(researchRuns).where(and91(
|
|
75848
|
+
eq108(researchRuns.projectId, project.id),
|
|
75623
75849
|
gte31(researchRuns.createdAt, start),
|
|
75624
75850
|
lt17(researchRuns.createdAt, end),
|
|
75625
75851
|
sql42`(json_extract(${researchRuns.initiatedBy}, '$.role') = ${UserRoles.viewer} OR json_extract(${researchRuns.initiatedBy}, '$.limited') = 1)`
|
|
@@ -75650,15 +75876,15 @@ async function researchRoutes(app, opts) {
|
|
|
75650
75876
|
if (input.idempotencyKey.startsWith(BATCH_RECEIPT_PREFIX)) throw validationError("This idempotency key prefix is reserved for internal research batch receipts.");
|
|
75651
75877
|
const requestHash2 = researchBatchRequestHash(input);
|
|
75652
75878
|
const receiptKeys = input.runs.map((_run, index) => batchChildReceiptKey(input.idempotencyKey, index));
|
|
75653
|
-
const firstReceipt = app.db.select().from(researchRuns).where(
|
|
75654
|
-
|
|
75655
|
-
|
|
75879
|
+
const firstReceipt = app.db.select().from(researchRuns).where(and91(
|
|
75880
|
+
eq108(researchRuns.projectId, project.id),
|
|
75881
|
+
eq108(researchRuns.idempotencyKey, receiptKeys[0])
|
|
75656
75882
|
)).get();
|
|
75657
75883
|
if (firstReceipt) {
|
|
75658
75884
|
if (firstReceipt.requestHash !== requestHash2) throw alreadyExists("Research batch idempotency key", input.idempotencyKey);
|
|
75659
|
-
const saved = receiptKeys.map((key) => app.db.select().from(researchRuns).where(
|
|
75660
|
-
|
|
75661
|
-
|
|
75885
|
+
const saved = receiptKeys.map((key) => app.db.select().from(researchRuns).where(and91(
|
|
75886
|
+
eq108(researchRuns.projectId, project.id),
|
|
75887
|
+
eq108(researchRuns.idempotencyKey, key)
|
|
75662
75888
|
)).get());
|
|
75663
75889
|
if (saved.some((row) => !row || row.requestHash !== requestHash2)) throw alreadyExists("Research batch idempotency key", input.idempotencyKey);
|
|
75664
75890
|
const runs3 = saved.map((row) => getDetail(app, project.id, row.id));
|
|
@@ -75674,8 +75900,8 @@ async function researchRoutes(app, opts) {
|
|
|
75674
75900
|
const decision = app.db.transaction((tx) => {
|
|
75675
75901
|
if (initiatedBy?.limited) {
|
|
75676
75902
|
const { start, end, date } = utcDayBounds(now);
|
|
75677
|
-
const used = tx.select({ value: count9() }).from(researchRuns).where(
|
|
75678
|
-
|
|
75903
|
+
const used = tx.select({ value: count9() }).from(researchRuns).where(and91(
|
|
75904
|
+
eq108(researchRuns.projectId, project.id),
|
|
75679
75905
|
gte31(researchRuns.createdAt, start),
|
|
75680
75906
|
lt17(researchRuns.createdAt, end),
|
|
75681
75907
|
sql42`(json_extract(${researchRuns.initiatedBy}, '$.role') = ${UserRoles.viewer} OR json_extract(${researchRuns.initiatedBy}, '$.limited') = 1)`
|
|
@@ -75729,9 +75955,9 @@ async function researchRoutes(app, opts) {
|
|
|
75729
75955
|
const requested = Number.parseInt(request.query.limit ?? "", 10);
|
|
75730
75956
|
const limit = Number.isInteger(requested) && requested > 0 ? Math.min(requested, 100) : 20;
|
|
75731
75957
|
const cursor = request.query.cursor ? parseResearchCursor(request.query.cursor, project.id) : null;
|
|
75732
|
-
const rows = app.db.select().from(researchRuns).where(
|
|
75733
|
-
|
|
75734
|
-
cursor ? or22(lt17(researchRuns.createdAt, cursor.createdAt),
|
|
75958
|
+
const rows = app.db.select().from(researchRuns).where(and91(
|
|
75959
|
+
eq108(researchRuns.projectId, project.id),
|
|
75960
|
+
cursor ? or22(lt17(researchRuns.createdAt, cursor.createdAt), and91(eq108(researchRuns.createdAt, cursor.createdAt), lt17(researchRuns.id, cursor.id))) : void 0
|
|
75735
75961
|
)).orderBy(desc49(researchRuns.createdAt), desc49(researchRuns.id)).limit(limit + 1).all();
|
|
75736
75962
|
const runs2 = rows.slice(0, limit).map(serializeRun);
|
|
75737
75963
|
const last = runs2.at(-1);
|
|
@@ -75881,9 +76107,9 @@ function resolveStoredResearchTemplate(stored, selection, idempotencyKey) {
|
|
|
75881
76107
|
return stored;
|
|
75882
76108
|
}
|
|
75883
76109
|
function resolveResearchTemplate(app, projectId, selection, scope, location) {
|
|
75884
|
-
const savedTemplate = app.db.select().from(measurementQueryTemplates).where(
|
|
75885
|
-
|
|
75886
|
-
|
|
76110
|
+
const savedTemplate = app.db.select().from(measurementQueryTemplates).where(and91(
|
|
76111
|
+
eq108(measurementQueryTemplates.projectId, projectId),
|
|
76112
|
+
eq108(measurementQueryTemplates.id, selection.templateId)
|
|
75887
76113
|
)).get();
|
|
75888
76114
|
if (!savedTemplate) throw validationError("Unknown or stale research template. Reload the selected template and try again.");
|
|
75889
76115
|
const templateVersion = savedTemplate.updatedAt;
|
|
@@ -75892,7 +76118,7 @@ function resolveResearchTemplate(app, projectId, selection, scope, location) {
|
|
|
75892
76118
|
}
|
|
75893
76119
|
const bindingLocation = selection.bindingLocation === void 0 ? location : selection.bindingLocation;
|
|
75894
76120
|
if (bindingLocation) {
|
|
75895
|
-
const project = app.db.select({ locations: projects.locations }).from(projects).where(
|
|
76121
|
+
const project = app.db.select({ locations: projects.locations }).from(projects).where(eq108(projects.id, projectId)).get();
|
|
75896
76122
|
if (!project.locations.some((item2) => sameLocation(item2, bindingLocation))) throw validationError("Research template binding location must match a configured project location.");
|
|
75897
76123
|
}
|
|
75898
76124
|
const { bindings, output } = expandResearchTemplate(savedTemplate, scope, bindingLocation);
|
|
@@ -75905,9 +76131,9 @@ function classifyResearchQueries(project, plan, queries2) {
|
|
|
75905
76131
|
return queries2.map((query) => classifier?.classify(query) ?? null);
|
|
75906
76132
|
}
|
|
75907
76133
|
function getDetail(app, projectId, id) {
|
|
75908
|
-
const row = app.db.select().from(researchRuns).where(
|
|
76134
|
+
const row = app.db.select().from(researchRuns).where(and91(eq108(researchRuns.id, id), eq108(researchRuns.projectId, projectId))).get();
|
|
75909
76135
|
if (!row) throw notFound("Research run", id);
|
|
75910
|
-
const queries2 = app.db.select().from(researchRunQueries).where(
|
|
76136
|
+
const queries2 = app.db.select().from(researchRunQueries).where(eq108(researchRunQueries.researchRunId, id)).orderBy(researchRunQueries.position).all().map(serializeQuery);
|
|
75911
76137
|
return { ...serializeRun(row), queries: queries2 };
|
|
75912
76138
|
}
|
|
75913
76139
|
function serializeRun(row) {
|
|
@@ -75962,7 +76188,7 @@ function utcDayBounds(now) {
|
|
|
75962
76188
|
|
|
75963
76189
|
// ../api-routes/src/simple-measurement-definitions.ts
|
|
75964
76190
|
import crypto60 from "crypto";
|
|
75965
|
-
import { and as
|
|
76191
|
+
import { and as and92, eq as eq109 } from "drizzle-orm";
|
|
75966
76192
|
function captureSimpleMeasurementDefinition(db, input) {
|
|
75967
76193
|
return db.transaction((tx) => {
|
|
75968
76194
|
const run = tx.select({
|
|
@@ -75970,7 +76196,7 @@ function captureSimpleMeasurementDefinition(db, input) {
|
|
|
75970
76196
|
trigger: runs.trigger,
|
|
75971
76197
|
status: runs.status,
|
|
75972
76198
|
measurementPlanVersionId: runs.measurementPlanVersionId
|
|
75973
|
-
}).from(runs).where(
|
|
76199
|
+
}).from(runs).where(and92(eq109(runs.projectId, input.projectId), eq109(runs.id, input.runId))).get();
|
|
75974
76200
|
if (!run) throw notFound("Run", input.runId);
|
|
75975
76201
|
if (run.kind !== RunKinds["answer-visibility"] || run.trigger === RunTriggers.probe || run.measurementPlanVersionId !== null) {
|
|
75976
76202
|
return null;
|
|
@@ -75991,7 +76217,7 @@ function captureSimpleMeasurementDefinition(db, input) {
|
|
|
75991
76217
|
throw validationError("Qualified aliases must be a sorted subset of the captured aliases.");
|
|
75992
76218
|
}
|
|
75993
76219
|
}
|
|
75994
|
-
const existing = tx.select().from(simpleMeasurementDefinitions).where(
|
|
76220
|
+
const existing = tx.select().from(simpleMeasurementDefinitions).where(and92(eq109(simpleMeasurementDefinitions.projectId, input.projectId), eq109(simpleMeasurementDefinitions.runId, input.runId))).get();
|
|
75995
76221
|
if (existing) {
|
|
75996
76222
|
const frozen = simpleMeasurementDefinitionSchema.parse(existing.definition);
|
|
75997
76223
|
const { qualifiedAliases: _liveQualifiedAliases, ...liveIdentity } = definition2.identity;
|
|
@@ -76017,15 +76243,15 @@ function captureSimpleMeasurementDefinition(db, input) {
|
|
|
76017
76243
|
if (run.status !== RunStatuses.running) {
|
|
76018
76244
|
throw validationError("Only a running run can capture a new measurement definition.");
|
|
76019
76245
|
}
|
|
76020
|
-
if (tx.select({ id: querySnapshots.id }).from(querySnapshots).where(
|
|
76246
|
+
if (tx.select({ id: querySnapshots.id }).from(querySnapshots).where(eq109(querySnapshots.runId, input.runId)).limit(1).get()) {
|
|
76021
76247
|
throw validationError("This run already has stored answers. Its measurement definition cannot be inferred afterward.");
|
|
76022
76248
|
}
|
|
76023
|
-
const projectQueryIds = new Set(tx.select({ id: queries.id }).from(queries).where(
|
|
76249
|
+
const projectQueryIds = new Set(tx.select({ id: queries.id }).from(queries).where(eq109(queries.projectId, input.projectId)).all().map((query) => query.id));
|
|
76024
76250
|
if (definition2.queries.some((query) => !projectQueryIds.has(query.queryId))) {
|
|
76025
76251
|
throw validationError("Every captured query must belong to the run project.");
|
|
76026
76252
|
}
|
|
76027
76253
|
if (definition2.competitors !== void 0) {
|
|
76028
|
-
const liveCompetitorDomains = new Set(tx.select({ domain: competitors.domain }).from(competitors).where(
|
|
76254
|
+
const liveCompetitorDomains = new Set(tx.select({ domain: competitors.domain }).from(competitors).where(eq109(competitors.projectId, input.projectId)).orderBy(competitors.domain).all().map((competitor) => competitor.domain));
|
|
76029
76255
|
const capturedDomains = new Set(definition2.competitors.map((competitor) => competitor.domain));
|
|
76030
76256
|
if (liveCompetitorDomains.size !== capturedDomains.size || [...liveCompetitorDomains].some((domain) => !capturedDomains.has(domain))) {
|
|
76031
76257
|
throw validationError("Captured competitors must exactly match the project competitors dispatched for this run.");
|
|
@@ -76368,6 +76594,7 @@ async function apiRoutes(app, opts) {
|
|
|
76368
76594
|
getCloudflareQueueRetentionSeconds: opts.getCloudflareQueueRetentionSeconds,
|
|
76369
76595
|
cloudflareQueueMaxBatches: opts.cloudflareQueueMaxBatches,
|
|
76370
76596
|
cloudflareQueueSyncDeadlineMs: opts.cloudflareQueueSyncDeadlineMs,
|
|
76597
|
+
cloudflareQueueEmptyPullBackoffMs: opts.cloudflareQueueEmptyPullBackoffMs,
|
|
76371
76598
|
cloudflareTrafficIngestUrl: opts.cloudflareTrafficIngestUrl,
|
|
76372
76599
|
cloudflareIngestRateLimitMax: opts.cloudflareIngestRateLimitMax,
|
|
76373
76600
|
cloudflareIngestIpRateLimitMax: opts.cloudflareIngestIpRateLimitMax,
|
|
@@ -76407,6 +76634,7 @@ async function apiRoutes(app, opts) {
|
|
|
76407
76634
|
await api.register(technicalAeoRoutes, {
|
|
76408
76635
|
onSiteAuditRequested: opts.onSiteAuditRequested
|
|
76409
76636
|
});
|
|
76637
|
+
await api.register(trendsRoutes);
|
|
76410
76638
|
await api.register(doctorRoutes, {
|
|
76411
76639
|
googleConnectionStore: opts.googleConnectionStore,
|
|
76412
76640
|
bingConnectionStore: opts.bingConnectionStore,
|
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
import {
|
|
2
2
|
createApiClient
|
|
3
|
-
} from "./chunk-
|
|
4
|
-
import "./chunk-
|
|
3
|
+
} from "./chunk-TEQF4ZJA.js";
|
|
4
|
+
import "./chunk-2FJB3CSS.js";
|
|
5
5
|
import "./chunk-AOJU5LKX.js";
|
|
6
6
|
import "./chunk-HGMYEDAQ.js";
|
|
7
|
-
import "./chunk-
|
|
7
|
+
import "./chunk-F643WELJ.js";
|
|
8
8
|
import "./chunk-MLKGABMK.js";
|
|
9
9
|
|
|
10
10
|
// src/commands/citations.ts
|