@canonry/canonry 7.86.0 → 7.87.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/assets/agent-workspace/skills/canonry/references/canonry-cli.md +19 -0
- 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-LIMS34H6.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-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-EX6GVZAE.js → chunk-CBWGUGME.js} +2 -1
- package/dist/{chunk-LJ6VNMCY.js → chunk-D5V6YQXA.js} +7 -7
- package/dist/{chunk-CBNFWSWU.js → chunk-DOPUEXMC.js} +2 -2
- package/dist/{chunk-UPFWF6TF.js → chunk-EJZHL67Y.js} +4 -4
- package/dist/{chunk-DU4QEC65.js → chunk-EQGCGZZY.js} +261 -50
- 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-HHMOUS26.js → chunk-ITOK75B6.js} +5 -5
- package/dist/{chunk-BLGYC2K7.js → chunk-JAYUWQAV.js} +1 -1
- package/dist/{chunk-GO4RP6XE.js → chunk-JDTKG7KJ.js} +7 -3
- package/dist/{chunk-ZFL5DOGJ.js → chunk-JGGDENAA.js} +395 -342
- package/dist/{chunk-RI2ZT3OR.js → chunk-KOBOKZXF.js} +6 -6
- package/dist/{chunk-4EKPEVJ2.js → chunk-L5NMS7AU.js} +5 -5
- package/dist/{chunk-F5SQITTJ.js → chunk-MNLAIB67.js} +1 -1
- package/dist/{chunk-SUSQEH2D.js → chunk-O26FQ7RI.js} +1 -1
- package/dist/{chunk-TTXBQCE6.js → chunk-OJKWT6U6.js} +21 -21
- package/dist/{chunk-VBW33U4E.js → chunk-TEQF4ZJA.js} +22 -2
- package/dist/{chunk-AZP37FMA.js → chunk-TN53B5AB.js} +28 -3
- package/dist/{chunk-MJE3SGFW.js → chunk-VDYXB6SV.js} +1 -1
- package/dist/{chunk-5KHF5FV5.js → chunk-YKB67YKH.js} +4 -4
- 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-J6KWSWIS.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-5QMYF4ZP.js} +6 -6
- package/dist/{demo-ZYK5XCP3.js → demo-KGQL5EJM.js} +1 -1
- package/dist/{demo-server-LZ6MWJ2S.js → demo-server-ERIR2T2N.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-HOBW5K2Q.js} +12 -12
- package/dist/{insights-636LLPVG.js → insights-XZQO4B45.js} +3 -3
- package/dist/intelligence-service-2AH42Y4Q.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-WF5ZXWW6.js} +26 -26
- package/dist/{session-3RTDTW6Z.js → session-HEDEP77R.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-KYG67FBW.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;
|
|
@@ -73303,6 +73405,15 @@ var FETCHED_SITE_CRAWL_STATES = /* @__PURE__ */ new Set([
|
|
|
73303
73405
|
"fetched"
|
|
73304
73406
|
]);
|
|
73305
73407
|
var SURFACEABLE_STATUSES = [RunStatuses.completed, RunStatuses.partial];
|
|
73408
|
+
function surfaceableSiteAuditFilters(projectId) {
|
|
73409
|
+
return [
|
|
73410
|
+
eq106(siteAuditSnapshots.projectId, projectId),
|
|
73411
|
+
eq106(runs.projectId, projectId),
|
|
73412
|
+
eq106(runs.kind, RunKinds["site-audit"]),
|
|
73413
|
+
inArray47(runs.status, SURFACEABLE_STATUSES),
|
|
73414
|
+
notProbeRun()
|
|
73415
|
+
];
|
|
73416
|
+
}
|
|
73306
73417
|
function siteAuditRunPhase(status, attempt, layoutState) {
|
|
73307
73418
|
switch (status) {
|
|
73308
73419
|
case RunStatuses.queued:
|
|
@@ -73714,11 +73825,7 @@ async function technicalAeoRoutes(app, opts) {
|
|
|
73714
73825
|
};
|
|
73715
73826
|
const resolveAudit = (projectId, runId, recordedBefore) => {
|
|
73716
73827
|
const filters = [
|
|
73717
|
-
|
|
73718
|
-
eq106(runs.projectId, projectId),
|
|
73719
|
-
eq106(runs.kind, RunKinds["site-audit"]),
|
|
73720
|
-
inArray47(runs.status, SURFACEABLE_STATUSES),
|
|
73721
|
-
notProbeRun(),
|
|
73828
|
+
...surfaceableSiteAuditFilters(projectId),
|
|
73722
73829
|
...recordedBefore ? [lt16(siteAuditSnapshots.createdAt, recordedBefore)] : []
|
|
73723
73830
|
];
|
|
73724
73831
|
const complete = sql41`case when ${runs.status} = ${RunStatuses.completed} then 1 else 0 end`;
|
|
@@ -74006,13 +74113,7 @@ async function technicalAeoRoutes(app, opts) {
|
|
|
74006
74113
|
auditedAt: siteAuditSnapshots.auditedAt,
|
|
74007
74114
|
aggregateScore: siteAuditSnapshots.aggregateScore,
|
|
74008
74115
|
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();
|
|
74116
|
+
}).from(siteAuditSnapshots).innerJoin(runs, eq106(siteAuditSnapshots.runId, runs.id)).where(and89(...surfaceableSiteAuditFilters(project.id))).orderBy(desc48(siteAuditSnapshots.createdAt)).limit(limit).all();
|
|
74016
74117
|
return { project: project.name, points: rows.reverse() };
|
|
74017
74118
|
});
|
|
74018
74119
|
app.get("/projects/:name/technical-aeo/crawl", async (request) => {
|
|
@@ -74748,8 +74849,8 @@ async function technicalAeoRoutes(app, opts) {
|
|
|
74748
74849
|
const internalPreviousJoin = entity === "link" ? sql41`AND previous.internal = 1` : sql41``;
|
|
74749
74850
|
const internalPrevious = entity === "link" ? sql41`AND previous.internal = 1` : sql41``;
|
|
74750
74851
|
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 ${
|
|
74852
|
+
const difference3 = entity === "page" ? PAGE_CHANGE_PREDICATE : LINK_CHANGE_PREDICATE;
|
|
74853
|
+
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
74854
|
const afterRows = includeAfter ? app.db.all(sql41`
|
|
74754
74855
|
SELECT ${entity} AS entity,
|
|
74755
74856
|
CASE WHEN previous.id IS NULL THEN 'added' ELSE 'changed' END AS change,
|
|
@@ -75553,10 +75654,119 @@ async function technicalAeoRoutes(app, opts) {
|
|
|
75553
75654
|
});
|
|
75554
75655
|
}
|
|
75555
75656
|
|
|
75657
|
+
// ../api-routes/src/trends.ts
|
|
75658
|
+
import { and as and90, asc as asc25, eq as eq107 } from "drizzle-orm";
|
|
75659
|
+
function parseSurface(value) {
|
|
75660
|
+
const parsed = trendSurfaceSchema.safeParse(value);
|
|
75661
|
+
if (!parsed.success) {
|
|
75662
|
+
throw validationError(`Invalid surface "${value ?? ""}". Must be one of: ${trendSurfaceSchema.options.join(", ")}.`, {
|
|
75663
|
+
field: "surface",
|
|
75664
|
+
value: value ?? null,
|
|
75665
|
+
allowed: [...trendSurfaceSchema.options]
|
|
75666
|
+
});
|
|
75667
|
+
}
|
|
75668
|
+
return parsed.data;
|
|
75669
|
+
}
|
|
75670
|
+
function parseDate(field, value) {
|
|
75671
|
+
if (value === void 0) return void 0;
|
|
75672
|
+
if (!isCalendarDate(value)) throw validationError(`Invalid ${field} "${value}". Use a calendar date, YYYY-MM-DD.`, { field, value });
|
|
75673
|
+
return value;
|
|
75674
|
+
}
|
|
75675
|
+
function parseSelection2(query, surface) {
|
|
75676
|
+
const startDate = parseDate("startDate", query.startDate);
|
|
75677
|
+
const endDate = parseDate("endDate", query.endDate);
|
|
75678
|
+
if (startDate === void 0 && endDate === void 0) {
|
|
75679
|
+
return parseWindowIn(query.window, WINDOW_SETS.metrics, { fallback: TREND_SURFACE_DEFAULT_WINDOW[surface] });
|
|
75680
|
+
}
|
|
75681
|
+
if (query.window !== void 0) {
|
|
75682
|
+
throw validationError("Pass window or a startDate/endDate range, not both.", { field: "window", value: query.window });
|
|
75683
|
+
}
|
|
75684
|
+
if (startDate && endDate && startDate > endDate) {
|
|
75685
|
+
throw validationError(`startDate ${startDate} is after endDate ${endDate}.`, { field: "startDate", value: startDate });
|
|
75686
|
+
}
|
|
75687
|
+
return { ...startDate ? { startDate } : {}, ...endDate ? { endDate } : {} };
|
|
75688
|
+
}
|
|
75689
|
+
async function trendsRoutes(app) {
|
|
75690
|
+
app.get("/projects/:name/trends", async (request) => {
|
|
75691
|
+
const project = resolveProject(app.db, request.params.name);
|
|
75692
|
+
const surface = parseSurface(request.query.surface);
|
|
75693
|
+
const selection = parseSelection2(request.query, surface);
|
|
75694
|
+
const groups = readSurface(app, project.id, surface).filter((group) => group.figures.some((figure) => figure.readings.some((reading) => reading.value !== null)));
|
|
75695
|
+
const span = trendWindowSpan(groups.flatMap((group) => group.figures.flatMap((figure) => figure.readings.filter((reading) => reading.value !== null).map((reading) => reading.date))), selection);
|
|
75696
|
+
const built = groups.map((group) => ({
|
|
75697
|
+
key: group.key,
|
|
75698
|
+
label: group.label,
|
|
75699
|
+
figures: group.figures.map((figure) => buildLevelTrendFigure({ ...figure, span }))
|
|
75700
|
+
}));
|
|
75701
|
+
return {
|
|
75702
|
+
project: project.name,
|
|
75703
|
+
surface,
|
|
75704
|
+
window: typeof selection === "string" ? selection : null,
|
|
75705
|
+
startDate: span?.startDate ?? null,
|
|
75706
|
+
endDate: span?.endDate ?? null,
|
|
75707
|
+
groups: built
|
|
75708
|
+
};
|
|
75709
|
+
});
|
|
75710
|
+
}
|
|
75711
|
+
function readSurface(app, projectId, surface) {
|
|
75712
|
+
switch (surface) {
|
|
75713
|
+
case TrendSurfaces["index-coverage"]: {
|
|
75714
|
+
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();
|
|
75715
|
+
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();
|
|
75716
|
+
const coverage = (key, label, rows) => ({
|
|
75717
|
+
key,
|
|
75718
|
+
label,
|
|
75719
|
+
figures: [
|
|
75720
|
+
{ key: "indexed", label: "Indexed", unit: TrendUnits.count, better: TrendBetter.higher, readings: rows.map((row) => ({ date: row.date, value: row.indexed })) },
|
|
75721
|
+
{ key: "not-indexed", label: "Not indexed", unit: TrendUnits.count, better: TrendBetter.lower, readings: rows.map((row) => ({ date: row.date, value: row.notIndexed })) }
|
|
75722
|
+
]
|
|
75723
|
+
});
|
|
75724
|
+
return [coverage("google", "Google", google), coverage("bing", "Bing", bing)];
|
|
75725
|
+
}
|
|
75726
|
+
case TrendSurfaces["site-health"]: {
|
|
75727
|
+
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();
|
|
75728
|
+
return [{
|
|
75729
|
+
key: "site-health",
|
|
75730
|
+
label: "Site Health",
|
|
75731
|
+
figures: [
|
|
75732
|
+
{ key: "score", label: "Site score", unit: TrendUnits.score, better: TrendBetter.higher, readings: rows.map((row) => ({ date: row.auditedAt, observedAt: row.auditedAt, value: row.aggregateScore })) },
|
|
75733
|
+
{ 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 })) }
|
|
75734
|
+
]
|
|
75735
|
+
}];
|
|
75736
|
+
}
|
|
75737
|
+
case TrendSurfaces.backlinks: {
|
|
75738
|
+
const totals = computeFilteredReleaseTotals(app.db, projectId, BacklinkSources.commoncrawl);
|
|
75739
|
+
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) => {
|
|
75740
|
+
const filtered = totals.get(row.release);
|
|
75741
|
+
const known = (filtered?.domainRows ?? 0) > 0 || row.totalLinkingDomains === 0;
|
|
75742
|
+
const windowEnd = releaseWindowEndMonth(row.release);
|
|
75743
|
+
return {
|
|
75744
|
+
date: windowEnd ?? row.queriedAt,
|
|
75745
|
+
observedAt: windowEnd ? null : row.queriedAt,
|
|
75746
|
+
label: formatReleaseWindow(row.release) ?? row.release,
|
|
75747
|
+
linkingDomains: known ? filtered?.linkingDomains ?? 0 : null,
|
|
75748
|
+
hosts: known ? filtered?.hosts ?? 0 : null,
|
|
75749
|
+
top10HostsShare: known ? filtered?.top10HostsShare ?? 0 : null
|
|
75750
|
+
};
|
|
75751
|
+
});
|
|
75752
|
+
const readings = (value) => rows.map((row) => ({ date: row.date, observedAt: row.observedAt, label: row.label, value: value(row) }));
|
|
75753
|
+
return [{
|
|
75754
|
+
key: "backlinks",
|
|
75755
|
+
label: "Backlinks",
|
|
75756
|
+
figures: [
|
|
75757
|
+
{ key: "referring-domains", label: "Referring domains", unit: TrendUnits.count, better: TrendBetter.higher, readings: readings((row) => row.linkingDomains) },
|
|
75758
|
+
{ key: "hosts", label: "Total linking hosts", unit: TrendUnits.count, better: TrendBetter.higher, readings: readings((row) => row.hosts) },
|
|
75759
|
+
{ key: "top-10-concentration", label: "Top-10 concentration", unit: TrendUnits.fraction, better: TrendBetter.neither, readings: readings((row) => row.top10HostsShare) }
|
|
75760
|
+
]
|
|
75761
|
+
}];
|
|
75762
|
+
}
|
|
75763
|
+
}
|
|
75764
|
+
}
|
|
75765
|
+
|
|
75556
75766
|
// ../api-routes/src/research.ts
|
|
75557
75767
|
import crypto59 from "crypto";
|
|
75558
75768
|
import { z as z7 } from "zod";
|
|
75559
|
-
import { and as
|
|
75769
|
+
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
75770
|
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
75771
|
var BATCH_RECEIPT_PREFIX = "__canonry_research_batch__:";
|
|
75562
75772
|
async function researchRoutes(app, opts) {
|
|
@@ -75575,7 +75785,7 @@ async function researchRoutes(app, opts) {
|
|
|
75575
75785
|
const input = parsed.data;
|
|
75576
75786
|
if (input.idempotencyKey?.startsWith(BATCH_RECEIPT_PREFIX)) throw validationError("This idempotency key prefix is reserved for internal research batch receipts.");
|
|
75577
75787
|
const requestHash2 = directResearchRequestHash(input);
|
|
75578
|
-
const existingReceipt = input.idempotencyKey ? app.db.select().from(researchRuns).where(
|
|
75788
|
+
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
75789
|
if (existingReceipt) {
|
|
75580
75790
|
assertSameDirectResearchRequest(existingReceipt, input, requestHash2);
|
|
75581
75791
|
const result2 = getDetail(app, project.id, existingReceipt.id);
|
|
@@ -75610,7 +75820,7 @@ async function researchRoutes(app, opts) {
|
|
|
75610
75820
|
const initiatedBy = researchPrincipal(request);
|
|
75611
75821
|
const decision = app.db.transaction((tx) => {
|
|
75612
75822
|
if (input.idempotencyKey) {
|
|
75613
|
-
const existing = tx.select().from(researchRuns).where(
|
|
75823
|
+
const existing = tx.select().from(researchRuns).where(and91(eq108(researchRuns.projectId, project.id), eq108(researchRuns.idempotencyKey, input.idempotencyKey))).get();
|
|
75614
75824
|
if (existing) {
|
|
75615
75825
|
assertSameDirectResearchRequest(existing, input, requestHash2);
|
|
75616
75826
|
return { reused: true, id: existing.id, shouldDispatch: existing.status === ResearchRunStatuses.queued };
|
|
@@ -75618,8 +75828,8 @@ async function researchRoutes(app, opts) {
|
|
|
75618
75828
|
}
|
|
75619
75829
|
if (initiatedBy?.limited) {
|
|
75620
75830
|
const { start, end, date } = utcDayBounds(now);
|
|
75621
|
-
const used = tx.select({ value: count9() }).from(researchRuns).where(
|
|
75622
|
-
|
|
75831
|
+
const used = tx.select({ value: count9() }).from(researchRuns).where(and91(
|
|
75832
|
+
eq108(researchRuns.projectId, project.id),
|
|
75623
75833
|
gte31(researchRuns.createdAt, start),
|
|
75624
75834
|
lt17(researchRuns.createdAt, end),
|
|
75625
75835
|
sql42`(json_extract(${researchRuns.initiatedBy}, '$.role') = ${UserRoles.viewer} OR json_extract(${researchRuns.initiatedBy}, '$.limited') = 1)`
|
|
@@ -75650,15 +75860,15 @@ async function researchRoutes(app, opts) {
|
|
|
75650
75860
|
if (input.idempotencyKey.startsWith(BATCH_RECEIPT_PREFIX)) throw validationError("This idempotency key prefix is reserved for internal research batch receipts.");
|
|
75651
75861
|
const requestHash2 = researchBatchRequestHash(input);
|
|
75652
75862
|
const receiptKeys = input.runs.map((_run, index) => batchChildReceiptKey(input.idempotencyKey, index));
|
|
75653
|
-
const firstReceipt = app.db.select().from(researchRuns).where(
|
|
75654
|
-
|
|
75655
|
-
|
|
75863
|
+
const firstReceipt = app.db.select().from(researchRuns).where(and91(
|
|
75864
|
+
eq108(researchRuns.projectId, project.id),
|
|
75865
|
+
eq108(researchRuns.idempotencyKey, receiptKeys[0])
|
|
75656
75866
|
)).get();
|
|
75657
75867
|
if (firstReceipt) {
|
|
75658
75868
|
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
|
-
|
|
75869
|
+
const saved = receiptKeys.map((key) => app.db.select().from(researchRuns).where(and91(
|
|
75870
|
+
eq108(researchRuns.projectId, project.id),
|
|
75871
|
+
eq108(researchRuns.idempotencyKey, key)
|
|
75662
75872
|
)).get());
|
|
75663
75873
|
if (saved.some((row) => !row || row.requestHash !== requestHash2)) throw alreadyExists("Research batch idempotency key", input.idempotencyKey);
|
|
75664
75874
|
const runs3 = saved.map((row) => getDetail(app, project.id, row.id));
|
|
@@ -75674,8 +75884,8 @@ async function researchRoutes(app, opts) {
|
|
|
75674
75884
|
const decision = app.db.transaction((tx) => {
|
|
75675
75885
|
if (initiatedBy?.limited) {
|
|
75676
75886
|
const { start, end, date } = utcDayBounds(now);
|
|
75677
|
-
const used = tx.select({ value: count9() }).from(researchRuns).where(
|
|
75678
|
-
|
|
75887
|
+
const used = tx.select({ value: count9() }).from(researchRuns).where(and91(
|
|
75888
|
+
eq108(researchRuns.projectId, project.id),
|
|
75679
75889
|
gte31(researchRuns.createdAt, start),
|
|
75680
75890
|
lt17(researchRuns.createdAt, end),
|
|
75681
75891
|
sql42`(json_extract(${researchRuns.initiatedBy}, '$.role') = ${UserRoles.viewer} OR json_extract(${researchRuns.initiatedBy}, '$.limited') = 1)`
|
|
@@ -75729,9 +75939,9 @@ async function researchRoutes(app, opts) {
|
|
|
75729
75939
|
const requested = Number.parseInt(request.query.limit ?? "", 10);
|
|
75730
75940
|
const limit = Number.isInteger(requested) && requested > 0 ? Math.min(requested, 100) : 20;
|
|
75731
75941
|
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),
|
|
75942
|
+
const rows = app.db.select().from(researchRuns).where(and91(
|
|
75943
|
+
eq108(researchRuns.projectId, project.id),
|
|
75944
|
+
cursor ? or22(lt17(researchRuns.createdAt, cursor.createdAt), and91(eq108(researchRuns.createdAt, cursor.createdAt), lt17(researchRuns.id, cursor.id))) : void 0
|
|
75735
75945
|
)).orderBy(desc49(researchRuns.createdAt), desc49(researchRuns.id)).limit(limit + 1).all();
|
|
75736
75946
|
const runs2 = rows.slice(0, limit).map(serializeRun);
|
|
75737
75947
|
const last = runs2.at(-1);
|
|
@@ -75881,9 +76091,9 @@ function resolveStoredResearchTemplate(stored, selection, idempotencyKey) {
|
|
|
75881
76091
|
return stored;
|
|
75882
76092
|
}
|
|
75883
76093
|
function resolveResearchTemplate(app, projectId, selection, scope, location) {
|
|
75884
|
-
const savedTemplate = app.db.select().from(measurementQueryTemplates).where(
|
|
75885
|
-
|
|
75886
|
-
|
|
76094
|
+
const savedTemplate = app.db.select().from(measurementQueryTemplates).where(and91(
|
|
76095
|
+
eq108(measurementQueryTemplates.projectId, projectId),
|
|
76096
|
+
eq108(measurementQueryTemplates.id, selection.templateId)
|
|
75887
76097
|
)).get();
|
|
75888
76098
|
if (!savedTemplate) throw validationError("Unknown or stale research template. Reload the selected template and try again.");
|
|
75889
76099
|
const templateVersion = savedTemplate.updatedAt;
|
|
@@ -75892,7 +76102,7 @@ function resolveResearchTemplate(app, projectId, selection, scope, location) {
|
|
|
75892
76102
|
}
|
|
75893
76103
|
const bindingLocation = selection.bindingLocation === void 0 ? location : selection.bindingLocation;
|
|
75894
76104
|
if (bindingLocation) {
|
|
75895
|
-
const project = app.db.select({ locations: projects.locations }).from(projects).where(
|
|
76105
|
+
const project = app.db.select({ locations: projects.locations }).from(projects).where(eq108(projects.id, projectId)).get();
|
|
75896
76106
|
if (!project.locations.some((item2) => sameLocation(item2, bindingLocation))) throw validationError("Research template binding location must match a configured project location.");
|
|
75897
76107
|
}
|
|
75898
76108
|
const { bindings, output } = expandResearchTemplate(savedTemplate, scope, bindingLocation);
|
|
@@ -75905,9 +76115,9 @@ function classifyResearchQueries(project, plan, queries2) {
|
|
|
75905
76115
|
return queries2.map((query) => classifier?.classify(query) ?? null);
|
|
75906
76116
|
}
|
|
75907
76117
|
function getDetail(app, projectId, id) {
|
|
75908
|
-
const row = app.db.select().from(researchRuns).where(
|
|
76118
|
+
const row = app.db.select().from(researchRuns).where(and91(eq108(researchRuns.id, id), eq108(researchRuns.projectId, projectId))).get();
|
|
75909
76119
|
if (!row) throw notFound("Research run", id);
|
|
75910
|
-
const queries2 = app.db.select().from(researchRunQueries).where(
|
|
76120
|
+
const queries2 = app.db.select().from(researchRunQueries).where(eq108(researchRunQueries.researchRunId, id)).orderBy(researchRunQueries.position).all().map(serializeQuery);
|
|
75911
76121
|
return { ...serializeRun(row), queries: queries2 };
|
|
75912
76122
|
}
|
|
75913
76123
|
function serializeRun(row) {
|
|
@@ -75962,7 +76172,7 @@ function utcDayBounds(now) {
|
|
|
75962
76172
|
|
|
75963
76173
|
// ../api-routes/src/simple-measurement-definitions.ts
|
|
75964
76174
|
import crypto60 from "crypto";
|
|
75965
|
-
import { and as
|
|
76175
|
+
import { and as and92, eq as eq109 } from "drizzle-orm";
|
|
75966
76176
|
function captureSimpleMeasurementDefinition(db, input) {
|
|
75967
76177
|
return db.transaction((tx) => {
|
|
75968
76178
|
const run = tx.select({
|
|
@@ -75970,7 +76180,7 @@ function captureSimpleMeasurementDefinition(db, input) {
|
|
|
75970
76180
|
trigger: runs.trigger,
|
|
75971
76181
|
status: runs.status,
|
|
75972
76182
|
measurementPlanVersionId: runs.measurementPlanVersionId
|
|
75973
|
-
}).from(runs).where(
|
|
76183
|
+
}).from(runs).where(and92(eq109(runs.projectId, input.projectId), eq109(runs.id, input.runId))).get();
|
|
75974
76184
|
if (!run) throw notFound("Run", input.runId);
|
|
75975
76185
|
if (run.kind !== RunKinds["answer-visibility"] || run.trigger === RunTriggers.probe || run.measurementPlanVersionId !== null) {
|
|
75976
76186
|
return null;
|
|
@@ -75991,7 +76201,7 @@ function captureSimpleMeasurementDefinition(db, input) {
|
|
|
75991
76201
|
throw validationError("Qualified aliases must be a sorted subset of the captured aliases.");
|
|
75992
76202
|
}
|
|
75993
76203
|
}
|
|
75994
|
-
const existing = tx.select().from(simpleMeasurementDefinitions).where(
|
|
76204
|
+
const existing = tx.select().from(simpleMeasurementDefinitions).where(and92(eq109(simpleMeasurementDefinitions.projectId, input.projectId), eq109(simpleMeasurementDefinitions.runId, input.runId))).get();
|
|
75995
76205
|
if (existing) {
|
|
75996
76206
|
const frozen = simpleMeasurementDefinitionSchema.parse(existing.definition);
|
|
75997
76207
|
const { qualifiedAliases: _liveQualifiedAliases, ...liveIdentity } = definition2.identity;
|
|
@@ -76017,15 +76227,15 @@ function captureSimpleMeasurementDefinition(db, input) {
|
|
|
76017
76227
|
if (run.status !== RunStatuses.running) {
|
|
76018
76228
|
throw validationError("Only a running run can capture a new measurement definition.");
|
|
76019
76229
|
}
|
|
76020
|
-
if (tx.select({ id: querySnapshots.id }).from(querySnapshots).where(
|
|
76230
|
+
if (tx.select({ id: querySnapshots.id }).from(querySnapshots).where(eq109(querySnapshots.runId, input.runId)).limit(1).get()) {
|
|
76021
76231
|
throw validationError("This run already has stored answers. Its measurement definition cannot be inferred afterward.");
|
|
76022
76232
|
}
|
|
76023
|
-
const projectQueryIds = new Set(tx.select({ id: queries.id }).from(queries).where(
|
|
76233
|
+
const projectQueryIds = new Set(tx.select({ id: queries.id }).from(queries).where(eq109(queries.projectId, input.projectId)).all().map((query) => query.id));
|
|
76024
76234
|
if (definition2.queries.some((query) => !projectQueryIds.has(query.queryId))) {
|
|
76025
76235
|
throw validationError("Every captured query must belong to the run project.");
|
|
76026
76236
|
}
|
|
76027
76237
|
if (definition2.competitors !== void 0) {
|
|
76028
|
-
const liveCompetitorDomains = new Set(tx.select({ domain: competitors.domain }).from(competitors).where(
|
|
76238
|
+
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
76239
|
const capturedDomains = new Set(definition2.competitors.map((competitor) => competitor.domain));
|
|
76030
76240
|
if (liveCompetitorDomains.size !== capturedDomains.size || [...liveCompetitorDomains].some((domain) => !capturedDomains.has(domain))) {
|
|
76031
76241
|
throw validationError("Captured competitors must exactly match the project competitors dispatched for this run.");
|
|
@@ -76407,6 +76617,7 @@ async function apiRoutes(app, opts) {
|
|
|
76407
76617
|
await api.register(technicalAeoRoutes, {
|
|
76408
76618
|
onSiteAuditRequested: opts.onSiteAuditRequested
|
|
76409
76619
|
});
|
|
76620
|
+
await api.register(trendsRoutes);
|
|
76410
76621
|
await api.register(doctorRoutes, {
|
|
76411
76622
|
googleConnectionStore: opts.googleConnectionStore,
|
|
76412
76623
|
bingConnectionStore: opts.bingConnectionStore,
|