@ainyc/canonry 4.51.0 → 4.51.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.
@@ -12540,6 +12540,9 @@ var GA4_DEFAULT_SYNC_DAYS = 30;
12540
12540
  var GA4_MAX_SYNC_DAYS = 90;
12541
12541
  var GA4_REQUEST_TIMEOUT_MS = 3e4;
12542
12542
  var GA4_MAX_PAGES = 50;
12543
+ var GA4_MAX_CONCURRENT_REQUESTS = 4;
12544
+ var GA4_MAX_RETRIES = 3;
12545
+ var GA4_INITIAL_RETRY_DELAY_MS = 1e3;
12543
12546
  var GA4_DIMENSIONS = {
12544
12547
  date: "date",
12545
12548
  landingPagePlusQueryString: "landingPagePlusQueryString",
@@ -12560,10 +12563,14 @@ var GA4_METRICS = {
12560
12563
  // ../integration-google-analytics/src/types.ts
12561
12564
  var GA4ApiError = class extends Error {
12562
12565
  status;
12563
- constructor(message, status) {
12566
+ /** Seconds the GA4 API asked us to wait before retrying. Populated from the
12567
+ * `Retry-After` response header on 429 and 5xx responses when present. */
12568
+ retryAfterSeconds;
12569
+ constructor(message, status, retryAfterSeconds) {
12564
12570
  super(message);
12565
12571
  this.name = "GA4ApiError";
12566
12572
  this.status = status;
12573
+ if (retryAfterSeconds !== void 0) this.retryAfterSeconds = retryAfterSeconds;
12567
12574
  }
12568
12575
  };
12569
12576
 
@@ -12659,81 +12666,155 @@ async function getAccessToken(clientEmail, privateKey) {
12659
12666
  function escapeRegExp2(str) {
12660
12667
  return str.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
12661
12668
  }
12669
+ var gaInFlight = 0;
12670
+ var gaWaitQueue = [];
12671
+ async function acquireGa4Slot() {
12672
+ if (gaInFlight < GA4_MAX_CONCURRENT_REQUESTS) {
12673
+ gaInFlight++;
12674
+ return;
12675
+ }
12676
+ await new Promise((resolve) => gaWaitQueue.push(resolve));
12677
+ }
12678
+ function releaseGa4Slot() {
12679
+ const next = gaWaitQueue.shift();
12680
+ if (next) {
12681
+ next();
12682
+ } else {
12683
+ gaInFlight--;
12684
+ }
12685
+ }
12686
+ async function withGa4Limit(fn) {
12687
+ await acquireGa4Slot();
12688
+ try {
12689
+ return await fn();
12690
+ } finally {
12691
+ releaseGa4Slot();
12692
+ }
12693
+ }
12694
+ function parseRetryAfter(headerValue) {
12695
+ if (!headerValue) return void 0;
12696
+ const trimmed = headerValue.trim();
12697
+ const asNum = Number(trimmed);
12698
+ if (Number.isFinite(asNum) && asNum >= 0) return asNum;
12699
+ const asDate = Date.parse(trimmed);
12700
+ if (!Number.isNaN(asDate)) {
12701
+ const seconds = Math.max(0, (asDate - Date.now()) / 1e3);
12702
+ return seconds;
12703
+ }
12704
+ return void 0;
12705
+ }
12706
+ function isRetryableGa4Error(err) {
12707
+ if (err instanceof GA4ApiError) {
12708
+ return err.status === 429 || err.status >= 500;
12709
+ }
12710
+ return false;
12711
+ }
12712
+ async function withGa4Retry(fn, errLabel) {
12713
+ let lastError;
12714
+ for (let attempt = 0; attempt <= GA4_MAX_RETRIES; attempt++) {
12715
+ try {
12716
+ return await fn();
12717
+ } catch (err) {
12718
+ lastError = err;
12719
+ if (attempt >= GA4_MAX_RETRIES || !isRetryableGa4Error(err)) throw err;
12720
+ const ga4Err = err;
12721
+ const computedDelayMs = GA4_INITIAL_RETRY_DELAY_MS * Math.pow(2, attempt);
12722
+ const delayMs = ga4Err.retryAfterSeconds !== void 0 ? Math.max(0, ga4Err.retryAfterSeconds * 1e3) : computedDelayMs;
12723
+ ga4Log("info", `${errLabel}.retry`, {
12724
+ attempt: attempt + 1,
12725
+ maxAttempts: GA4_MAX_RETRIES + 1,
12726
+ status: ga4Err.status,
12727
+ delayMs,
12728
+ usedRetryAfter: ga4Err.retryAfterSeconds !== void 0
12729
+ });
12730
+ await new Promise((resolve) => setTimeout(resolve, delayMs));
12731
+ }
12732
+ }
12733
+ throw lastError;
12734
+ }
12662
12735
  async function runReport(accessToken, propertyId, request) {
12663
12736
  validatePropertyId(propertyId);
12664
12737
  const safePropertyId = encodeURIComponent(propertyId);
12665
12738
  const url = `${GA4_DATA_API_BASE}/properties/${safePropertyId}:runReport`;
12666
- const res = await fetch(url, {
12667
- method: "POST",
12668
- headers: {
12669
- "Authorization": `Bearer ${accessToken}`,
12670
- "Content-Type": "application/json"
12671
- },
12672
- body: JSON.stringify(request),
12673
- signal: AbortSignal.timeout(GA4_REQUEST_TIMEOUT_MS)
12674
- });
12675
- if (res.status === 401 || res.status === 403) {
12676
- const body = await res.text().catch(() => "");
12677
- let detail = "";
12678
- try {
12679
- const parsed = JSON.parse(body);
12680
- if (parsed.error?.status === "SERVICE_DISABLED") {
12681
- detail = " The Google Analytics Data API is not enabled for this GCP project. Enable it at: https://console.developers.google.com/apis/api/analyticsdata.googleapis.com/overview";
12682
- } else if (parsed.error?.message) {
12683
- detail = ` ${parsed.error.message}`;
12739
+ return withGa4Limit(() => withGa4Retry(async () => {
12740
+ const res = await fetch(url, {
12741
+ method: "POST",
12742
+ headers: {
12743
+ "Authorization": `Bearer ${accessToken}`,
12744
+ "Content-Type": "application/json"
12745
+ },
12746
+ body: JSON.stringify(request),
12747
+ signal: AbortSignal.timeout(GA4_REQUEST_TIMEOUT_MS)
12748
+ });
12749
+ if (res.status === 401 || res.status === 403) {
12750
+ const body = await res.text().catch(() => "");
12751
+ let detail = "";
12752
+ try {
12753
+ const parsed = JSON.parse(body);
12754
+ if (parsed.error?.status === "SERVICE_DISABLED") {
12755
+ detail = " The Google Analytics Data API is not enabled for this GCP project. Enable it at: https://console.developers.google.com/apis/api/analyticsdata.googleapis.com/overview";
12756
+ } else if (parsed.error?.message) {
12757
+ detail = ` ${parsed.error.message}`;
12758
+ }
12759
+ } catch {
12760
+ if (body.length < 200) detail = ` ${body}`;
12684
12761
  }
12685
- } catch {
12686
- if (body.length < 200) detail = ` ${body}`;
12762
+ ga4Log("error", "report.auth-failed", { propertyId, httpStatus: res.status });
12763
+ throw new GA4ApiError(
12764
+ `GA4 API authentication failed \u2014 check service account permissions.${detail}`,
12765
+ res.status
12766
+ );
12687
12767
  }
12688
- ga4Log("error", "report.auth-failed", { propertyId, httpStatus: res.status });
12689
- throw new GA4ApiError(
12690
- `GA4 API authentication failed \u2014 check service account permissions.${detail}`,
12691
- res.status
12692
- );
12693
- }
12694
- if (res.status === 429) {
12695
- ga4Log("error", "report.rate-limited", { propertyId });
12696
- throw new GA4ApiError("GA4 API rate limit exceeded", 429);
12697
- }
12698
- if (!res.ok) {
12699
- const body = await res.text();
12700
- ga4Log("error", "report.error", { propertyId, httpStatus: res.status });
12701
- const detail = body.length <= 500 ? body : `${body.slice(0, 500)}... [truncated]`;
12702
- throw new GA4ApiError(`GA4 API error (${res.status}): ${detail}`, res.status);
12703
- }
12704
- return await res.json();
12768
+ if (res.status === 429) {
12769
+ const retryAfterSeconds = parseRetryAfter(res.headers.get("retry-after"));
12770
+ ga4Log("error", "report.rate-limited", { propertyId, retryAfterSeconds });
12771
+ throw new GA4ApiError("GA4 API rate limit exceeded", 429, retryAfterSeconds);
12772
+ }
12773
+ if (!res.ok) {
12774
+ const body = await res.text();
12775
+ const retryAfterSeconds = res.status >= 500 ? parseRetryAfter(res.headers.get("retry-after")) : void 0;
12776
+ ga4Log("error", "report.error", { propertyId, httpStatus: res.status, retryAfterSeconds });
12777
+ const detail = body.length <= 500 ? body : `${body.slice(0, 500)}... [truncated]`;
12778
+ throw new GA4ApiError(`GA4 API error (${res.status}): ${detail}`, res.status, retryAfterSeconds);
12779
+ }
12780
+ return await res.json();
12781
+ }, "report"));
12705
12782
  }
12706
12783
  async function batchRunReports(accessToken, propertyId, requests) {
12707
12784
  const url = `${GA4_DATA_API_BASE}/properties/${propertyId}:batchRunReports`;
12708
- const res = await fetch(url, {
12709
- method: "POST",
12710
- headers: {
12711
- "Authorization": `Bearer ${accessToken}`,
12712
- "Content-Type": "application/json"
12713
- },
12714
- body: JSON.stringify({ requests }),
12715
- signal: AbortSignal.timeout(GA4_REQUEST_TIMEOUT_MS)
12716
- });
12717
- if (res.status === 401 || res.status === 403) {
12718
- const body = await res.text().catch(() => "");
12719
- ga4Log("error", "batch-report.auth-failed", { propertyId, httpStatus: res.status });
12720
- throw new GA4ApiError(
12721
- `GA4 API authentication failed \u2014 check service account permissions. ${body}`,
12722
- res.status
12723
- );
12724
- }
12725
- if (res.status === 429) {
12726
- ga4Log("error", "batch-report.rate-limited", { propertyId });
12727
- throw new GA4ApiError("GA4 API rate limit exceeded", 429);
12728
- }
12729
- if (!res.ok) {
12730
- const body = await res.text();
12731
- ga4Log("error", "batch-report.error", { propertyId, httpStatus: res.status });
12732
- const detail = body.length <= 500 ? body : `${body.slice(0, 500)}... [truncated]`;
12733
- throw new GA4ApiError(`GA4 API error (${res.status}): ${detail}`, res.status);
12734
- }
12735
- const data = await res.json();
12736
- return data.reports;
12785
+ return withGa4Limit(() => withGa4Retry(async () => {
12786
+ const res = await fetch(url, {
12787
+ method: "POST",
12788
+ headers: {
12789
+ "Authorization": `Bearer ${accessToken}`,
12790
+ "Content-Type": "application/json"
12791
+ },
12792
+ body: JSON.stringify({ requests }),
12793
+ signal: AbortSignal.timeout(GA4_REQUEST_TIMEOUT_MS)
12794
+ });
12795
+ if (res.status === 401 || res.status === 403) {
12796
+ const body = await res.text().catch(() => "");
12797
+ ga4Log("error", "batch-report.auth-failed", { propertyId, httpStatus: res.status });
12798
+ throw new GA4ApiError(
12799
+ `GA4 API authentication failed \u2014 check service account permissions. ${body}`,
12800
+ res.status
12801
+ );
12802
+ }
12803
+ if (res.status === 429) {
12804
+ const retryAfterSeconds = parseRetryAfter(res.headers.get("retry-after"));
12805
+ ga4Log("error", "batch-report.rate-limited", { propertyId, retryAfterSeconds });
12806
+ throw new GA4ApiError("GA4 API rate limit exceeded", 429, retryAfterSeconds);
12807
+ }
12808
+ if (!res.ok) {
12809
+ const body = await res.text();
12810
+ const retryAfterSeconds = res.status >= 500 ? parseRetryAfter(res.headers.get("retry-after")) : void 0;
12811
+ ga4Log("error", "batch-report.error", { propertyId, httpStatus: res.status, retryAfterSeconds });
12812
+ const detail = body.length <= 500 ? body : `${body.slice(0, 500)}... [truncated]`;
12813
+ throw new GA4ApiError(`GA4 API error (${res.status}): ${detail}`, res.status, retryAfterSeconds);
12814
+ }
12815
+ const data = await res.json();
12816
+ return data.reports;
12817
+ }, "batch-report"));
12737
12818
  }
12738
12819
  function formatDate2(d) {
12739
12820
  return d.toISOString().slice(0, 10);
package/dist/cli.js CHANGED
@@ -21,7 +21,7 @@ import {
21
21
  setTelemetrySource,
22
22
  showFirstRunNotice,
23
23
  trackEvent
24
- } from "./chunk-FDR3G6SB.js";
24
+ } from "./chunk-JOKPYAEL.js";
25
25
  import {
26
26
  CliError,
27
27
  EXIT_SYSTEM_ERROR,
package/dist/index.js CHANGED
@@ -1,6 +1,6 @@
1
1
  import {
2
2
  createServer
3
- } from "./chunk-FDR3G6SB.js";
3
+ } from "./chunk-JOKPYAEL.js";
4
4
  import {
5
5
  loadConfig
6
6
  } from "./chunk-GGXU5VKI.js";
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ainyc/canonry",
3
- "version": "4.51.0",
3
+ "version": "4.51.1",
4
4
  "type": "module",
5
5
  "description": "Agent-first open-source AEO operating platform - track how answer engines cite your domain",
6
6
  "license": "FSL-1.1-ALv2",
@@ -63,21 +63,21 @@
63
63
  "tsx": "^4.19.0",
64
64
  "@ainyc/canonry-api-routes": "0.0.0",
65
65
  "@ainyc/canonry-config": "0.0.0",
66
- "@ainyc/canonry-intelligence": "0.0.0",
67
- "@ainyc/canonry-contracts": "0.0.0",
68
66
  "@ainyc/canonry-db": "0.0.0",
69
- "@ainyc/canonry-integration-bing": "0.0.0",
70
- "@ainyc/canonry-integration-commoncrawl": "0.0.0",
67
+ "@ainyc/canonry-intelligence": "0.0.0",
71
68
  "@ainyc/canonry-integration-cloud-run": "0.0.0",
72
- "@ainyc/canonry-integration-google": "0.0.0",
69
+ "@ainyc/canonry-integration-commoncrawl": "0.0.0",
73
70
  "@ainyc/canonry-integration-traffic": "0.0.0",
74
- "@ainyc/canonry-provider-cdp": "0.0.0",
71
+ "@ainyc/canonry-integration-bing": "0.0.0",
72
+ "@ainyc/canonry-integration-google": "0.0.0",
75
73
  "@ainyc/canonry-integration-wordpress": "0.0.0",
76
74
  "@ainyc/canonry-provider-claude": "0.0.0",
77
- "@ainyc/canonry-provider-local": "0.0.0",
78
- "@ainyc/canonry-provider-openai": "0.0.0",
75
+ "@ainyc/canonry-provider-cdp": "0.0.0",
76
+ "@ainyc/canonry-contracts": "0.0.0",
79
77
  "@ainyc/canonry-provider-gemini": "0.0.0",
80
- "@ainyc/canonry-provider-perplexity": "0.0.0"
78
+ "@ainyc/canonry-provider-local": "0.0.0",
79
+ "@ainyc/canonry-provider-perplexity": "0.0.0",
80
+ "@ainyc/canonry-provider-openai": "0.0.0"
81
81
  },
82
82
  "scripts": {
83
83
  "build": "tsx scripts/copy-agent-assets.ts && tsup && tsx build-web.ts",