@gscdump/sdk 0.38.2 → 0.39.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/dist/index.mjs CHANGED
@@ -49,6 +49,9 @@ function tablesQuery(tablesOrOptions, options) {
49
49
  ...dateRangeOptionsQuery(source)
50
50
  };
51
51
  if (tables) query.tables = Array.isArray(tables) ? [...new Set(tables.filter(Boolean))].sort().join(",") : [...new Set(tables.split(",").map((t) => t.trim()).filter(Boolean))].sort().join(",");
52
+ if (source?.maxBytes != null) query.maxBytes = String(Math.max(1, Math.floor(source.maxBytes)));
53
+ if (source?.maxRows != null) query.maxRows = String(Math.max(1, Math.floor(source.maxRows)));
54
+ if (source?.maxFiles != null) query.maxFiles = String(Math.max(1, Math.floor(source.maxFiles)));
52
55
  return query;
53
56
  }
54
57
  function dataQuery(state, options) {
@@ -1133,19 +1136,26 @@ function coerceRowMetrics(row) {
1133
1136
  };
1134
1137
  }
1135
1138
  function summarizeDailyRows(raw) {
1136
- const daily = raw.map(coerceRowMetrics).map((r) => ({
1137
- date: r.date,
1138
- clicks: r.clicks,
1139
- impressions: r.impressions,
1140
- sum_position: r.sum_position
1141
- })).sort((a, b) => a.date.localeCompare(b.date));
1139
+ const daily = [];
1140
+ for (let index = 0; index < raw.length; index++) {
1141
+ const row = raw[index];
1142
+ const position = row.position ?? 0;
1143
+ const sumPosition = row.sum_position ?? (position > 0 ? (position - 1) * row.impressions : 0);
1144
+ daily.push({
1145
+ date: row.date,
1146
+ clicks: row.clicks,
1147
+ impressions: row.impressions,
1148
+ sum_position: sumPosition
1149
+ });
1150
+ }
1151
+ daily.sort((a, b) => a.date.localeCompare(b.date));
1142
1152
  let clicks = 0;
1143
1153
  let impressions = 0;
1144
1154
  let weightedPosition = 0;
1145
- for (const d of daily) {
1146
- clicks += d.clicks;
1147
- impressions += d.impressions;
1148
- weightedPosition += d.sum_position;
1155
+ for (const row of daily) {
1156
+ clicks += row.clicks;
1157
+ impressions += row.impressions;
1158
+ weightedPosition += row.sum_position;
1149
1159
  }
1150
1160
  return {
1151
1161
  daily,
@@ -1893,120 +1903,123 @@ function totalSitemapErrors(sitemaps) {
1893
1903
  function signedPct1(value) {
1894
1904
  return `${value >= 0 ? "+" : ""}${value.toFixed(1)}%`;
1895
1905
  }
1906
+ const SEARCH_CONSOLE_STAGES = {
1907
+ not_connected: {
1908
+ label: "Not connected",
1909
+ severity: "neutral",
1910
+ summary: "Search Console is not connected for this site yet.",
1911
+ primaryAction: "Connect Google Search Console so we can read discovery, indexing, and performance data.",
1912
+ nextStage: "waiting_for_data",
1913
+ sprintFindingTypes: []
1914
+ },
1915
+ waiting_for_data: {
1916
+ label: "Waiting for data",
1917
+ severity: "info",
1918
+ summary: "Search Console is connected, but there is not enough indexing data to diagnose the site yet.",
1919
+ primaryAction: "Let the first sync finish, then make sure a sitemap is submitted.",
1920
+ nextStage: "weak_discovery",
1921
+ sprintFindingTypes: []
1922
+ },
1923
+ weak_discovery: {
1924
+ label: "Weak discovery",
1925
+ severity: "warning",
1926
+ summary: "Google does not have a clean map of the pages you want indexed.",
1927
+ primaryAction: "Submit a clean sitemap that contains only canonical, indexable 200 URLs.",
1928
+ nextStage: "discovery_backlog",
1929
+ sprintFindingTypes: ["search-console-stage", "sitemap-missing"]
1930
+ },
1931
+ discovery_backlog: {
1932
+ label: "Discovery backlog",
1933
+ severity: "warning",
1934
+ summary: "Google knows these pages exist, but is not crawling them fast enough.",
1935
+ primaryAction: "Add internal links from indexed pages and remove low-value URLs from the sitemap.",
1936
+ nextStage: "crawl_blocked",
1937
+ sprintFindingTypes: ["search-console-stage"]
1938
+ },
1939
+ crawl_blocked: {
1940
+ label: "Crawl blocked",
1941
+ severity: "error",
1942
+ summary: "Google is trying to access pages, but technical access problems are blocking progress.",
1943
+ primaryAction: "Fix robots.txt blocks, server errors, broken URLs, and access failures before content work.",
1944
+ nextStage: "indexability_blocked",
1945
+ sprintFindingTypes: ["search-console-stage", "noindex-block"]
1946
+ },
1947
+ indexability_blocked: {
1948
+ label: "Indexability blocked",
1949
+ severity: "error",
1950
+ summary: "Google can reach pages, but index directives or canonical signals are preventing clean indexing.",
1951
+ primaryAction: "Remove accidental noindex directives and make canonical signals agree.",
1952
+ nextStage: "index_rejection",
1953
+ sprintFindingTypes: [
1954
+ "search-console-stage",
1955
+ "noindex-block",
1956
+ "canonicalisation"
1957
+ ]
1958
+ },
1959
+ index_rejection: {
1960
+ label: "Index rejection",
1961
+ severity: "warning",
1962
+ summary: "Google is crawling pages but skipping too many of them from the index.",
1963
+ primaryAction: "Improve or consolidate crawled-but-not-indexed pages before publishing more.",
1964
+ nextStage: "partially_indexed",
1965
+ sprintFindingTypes: ["search-console-stage", "pages-not-indexed"]
1966
+ },
1967
+ partially_indexed: {
1968
+ label: "Partially indexed",
1969
+ severity: "warning",
1970
+ summary: "A meaningful share of the site is indexed, but coverage is still below a healthy level.",
1971
+ primaryAction: "Work through the largest remaining indexing blocker first.",
1972
+ nextStage: "indexed_invisible",
1973
+ sprintFindingTypes: ["search-console-stage", "pages-not-indexed"]
1974
+ },
1975
+ indexed_invisible: {
1976
+ label: "Indexed but invisible",
1977
+ severity: "warning",
1978
+ summary: "Pages are indexed, but too many are not earning impressions in Search.",
1979
+ primaryAction: "Improve query targeting, titles, headings, internal links, and page depth.",
1980
+ nextStage: "visible_not_clicked",
1981
+ sprintFindingTypes: ["search-console-stage"]
1982
+ },
1983
+ visible_not_clicked: {
1984
+ label: "Visible but not clicked",
1985
+ severity: "warning",
1986
+ summary: "Google is showing your pages, but searchers are not clicking often enough.",
1987
+ primaryAction: "Rewrite titles and descriptions for the queries already producing impressions.",
1988
+ nextStage: "ranking_stalled",
1989
+ sprintFindingTypes: ["search-console-stage", "ctr-outliers"]
1990
+ },
1991
+ ranking_stalled: {
1992
+ label: "Ranking but stalled",
1993
+ severity: "info",
1994
+ summary: "The site has search visibility, but many pages are not yet ranking in useful positions.",
1995
+ primaryAction: "Prioritise striking-distance pages, refresh content, and add internal links.",
1996
+ nextStage: "healthy_growth_ready",
1997
+ sprintFindingTypes: ["striking-distance", "internal-linking"]
1998
+ },
1999
+ declining_visibility: {
2000
+ label: "Declining visibility",
2001
+ severity: "error",
2002
+ summary: "Search visibility is dropping compared with the previous period.",
2003
+ primaryAction: "Review affected pages, recent releases, competitors, and SERP changes before expanding.",
2004
+ nextStage: "healthy_growth_ready",
2005
+ sprintFindingTypes: ["search-console-stage", "negative-movers"]
2006
+ },
2007
+ healthy_growth_ready: {
2008
+ label: "Healthy",
2009
+ severity: "success",
2010
+ summary: "Google can discover, index, and show your pages.",
2011
+ primaryAction: "Push striking-distance pages (positions 11 to 20) and close content gaps to grow impressions.",
2012
+ nextStage: null,
2013
+ sprintFindingTypes: ["striking-distance", "competitor-content-gap"]
2014
+ }
2015
+ };
1896
2016
  function stage(key, evidence) {
2017
+ const definition = SEARCH_CONSOLE_STAGES[key];
1897
2018
  return {
1898
2019
  key,
1899
2020
  evidence,
1900
- ...{
1901
- not_connected: {
1902
- label: "Not connected",
1903
- severity: "neutral",
1904
- summary: "Search Console is not connected for this site yet.",
1905
- primaryAction: "Connect Google Search Console so we can read discovery, indexing, and performance data.",
1906
- nextStage: "waiting_for_data",
1907
- sprintFindingTypes: []
1908
- },
1909
- waiting_for_data: {
1910
- label: "Waiting for data",
1911
- severity: "info",
1912
- summary: "Search Console is connected, but there is not enough indexing data to diagnose the site yet.",
1913
- primaryAction: "Let the first sync finish, then make sure a sitemap is submitted.",
1914
- nextStage: "weak_discovery",
1915
- sprintFindingTypes: []
1916
- },
1917
- weak_discovery: {
1918
- label: "Weak discovery",
1919
- severity: "warning",
1920
- summary: "Google does not have a clean map of the pages you want indexed.",
1921
- primaryAction: "Submit a clean sitemap that contains only canonical, indexable 200 URLs.",
1922
- nextStage: "discovery_backlog",
1923
- sprintFindingTypes: ["search-console-stage", "sitemap-missing"]
1924
- },
1925
- discovery_backlog: {
1926
- label: "Discovery backlog",
1927
- severity: "warning",
1928
- summary: "Google knows these pages exist, but is not crawling them fast enough.",
1929
- primaryAction: "Add internal links from indexed pages and remove low-value URLs from the sitemap.",
1930
- nextStage: "crawl_blocked",
1931
- sprintFindingTypes: ["search-console-stage"]
1932
- },
1933
- crawl_blocked: {
1934
- label: "Crawl blocked",
1935
- severity: "error",
1936
- summary: "Google is trying to access pages, but technical access problems are blocking progress.",
1937
- primaryAction: "Fix robots.txt blocks, server errors, broken URLs, and access failures before content work.",
1938
- nextStage: "indexability_blocked",
1939
- sprintFindingTypes: ["search-console-stage", "noindex-block"]
1940
- },
1941
- indexability_blocked: {
1942
- label: "Indexability blocked",
1943
- severity: "error",
1944
- summary: "Google can reach pages, but index directives or canonical signals are preventing clean indexing.",
1945
- primaryAction: "Remove accidental noindex directives and make canonical signals agree.",
1946
- nextStage: "index_rejection",
1947
- sprintFindingTypes: [
1948
- "search-console-stage",
1949
- "noindex-block",
1950
- "canonicalisation"
1951
- ]
1952
- },
1953
- index_rejection: {
1954
- label: "Index rejection",
1955
- severity: "warning",
1956
- summary: "Google is crawling pages but skipping too many of them from the index.",
1957
- primaryAction: "Improve or consolidate crawled-but-not-indexed pages before publishing more.",
1958
- nextStage: "partially_indexed",
1959
- sprintFindingTypes: ["search-console-stage", "pages-not-indexed"]
1960
- },
1961
- partially_indexed: {
1962
- label: "Partially indexed",
1963
- severity: "warning",
1964
- summary: "A meaningful share of the site is indexed, but coverage is still below a healthy level.",
1965
- primaryAction: "Work through the largest remaining indexing blocker first.",
1966
- nextStage: "indexed_invisible",
1967
- sprintFindingTypes: ["search-console-stage", "pages-not-indexed"]
1968
- },
1969
- indexed_invisible: {
1970
- label: "Indexed but invisible",
1971
- severity: "warning",
1972
- summary: "Pages are indexed, but too many are not earning impressions in Search.",
1973
- primaryAction: "Improve query targeting, titles, headings, internal links, and page depth.",
1974
- nextStage: "visible_not_clicked",
1975
- sprintFindingTypes: ["search-console-stage"]
1976
- },
1977
- visible_not_clicked: {
1978
- label: "Visible but not clicked",
1979
- severity: "warning",
1980
- summary: "Google is showing your pages, but searchers are not clicking often enough.",
1981
- primaryAction: "Rewrite titles and descriptions for the queries already producing impressions.",
1982
- nextStage: "ranking_stalled",
1983
- sprintFindingTypes: ["search-console-stage", "ctr-outliers"]
1984
- },
1985
- ranking_stalled: {
1986
- label: "Ranking but stalled",
1987
- severity: "info",
1988
- summary: "The site has search visibility, but many pages are not yet ranking in useful positions.",
1989
- primaryAction: "Prioritise striking-distance pages, refresh content, and add internal links.",
1990
- nextStage: "healthy_growth_ready",
1991
- sprintFindingTypes: ["striking-distance", "internal-linking"]
1992
- },
1993
- declining_visibility: {
1994
- label: "Declining visibility",
1995
- severity: "error",
1996
- summary: "Search visibility is dropping compared with the previous period.",
1997
- primaryAction: "Review affected pages, recent releases, competitors, and SERP changes before expanding.",
1998
- nextStage: "healthy_growth_ready",
1999
- sprintFindingTypes: ["search-console-stage", "negative-movers"]
2000
- },
2001
- healthy_growth_ready: {
2002
- label: "Healthy",
2003
- severity: "success",
2004
- summary: "Google can discover, index, and show your pages.",
2005
- primaryAction: "Push striking-distance pages (positions 11 to 20) and close content gaps to grow impressions.",
2006
- nextStage: null,
2007
- sprintFindingTypes: ["striking-distance", "competitor-content-gap"]
2008
- }
2009
- }[key]
2021
+ ...definition,
2022
+ sprintFindingTypes: [...definition.sprintFindingTypes]
2010
2023
  };
2011
2024
  }
2012
2025
  const ESTABLISHED_IMPRESSIONS_28D$1 = 2e4;
@@ -2039,8 +2052,13 @@ function classifySearchConsoleStage(input) {
2039
2052
  const crawled = countSearchConsoleIssues(issues, "crawled_not_indexed");
2040
2053
  const hardBlocks = countSearchConsoleIssues(issues, "blocked_robots", "server_error", "access_denied", "forbidden") + (input.crawlAuditBlockerCount ?? 0);
2041
2054
  const canonicalMismatches = Math.min(notIndexed, input.canonicalMismatchCount ?? countSearchConsoleIssues(issues, "canonical_mismatch"));
2042
- const visibleNoClickPages = (input.pageInventory ?? []).filter((page) => page.impressions >= 50 && page.clicks === 0).length;
2043
- const poorPositionPages = (input.pageInventory ?? []).filter((page) => page.impressions >= 50 && (page.position ?? 0) > 20).length;
2055
+ let visibleNoClickPages = 0;
2056
+ let poorPositionPages = 0;
2057
+ for (const page of input.pageInventory ?? []) {
2058
+ if (page.impressions < 50) continue;
2059
+ if (page.clicks === 0) visibleNoClickPages++;
2060
+ if ((page.position ?? 0) > 20) poorPositionPages++;
2061
+ }
2044
2062
  const ctrOutlierCount = input.ctrOutlierCount ?? 0;
2045
2063
  const traj = input.trajectory ?? null;
2046
2064
  const impressions28d = input.impressions28d ?? null;
package/dist/v1/index.mjs CHANGED
@@ -155,11 +155,12 @@ async function resolveBaseHeaders(resolver) {
155
155
  }
156
156
  async function buildRequestHeaders(options, operation, prepared, executeOptions) {
157
157
  try {
158
- const headers = new Headers(await resolveBaseHeaders(options.headers));
158
+ const [baseHeaders, credential] = await Promise.all([resolveBaseHeaders(options.headers), resolveCredential(options.credential)]);
159
+ const headers = new Headers(baseHeaders);
159
160
  for (const [key, value] of Object.entries(prepared.headers)) if (value !== void 0) headers.set(key, String(value));
160
161
  headers.delete("x-api-key");
161
162
  headers.set("accept", "application/json");
162
- headers.set("authorization", `Bearer ${await resolveCredential(options.credential)}`);
163
+ headers.set("authorization", `Bearer ${credential}`);
163
164
  if (executeOptions.idempotencyKey) headers.set("idempotency-key", executeOptions.idempotencyKey);
164
165
  if (prepared.body !== void 0) headers.set("content-type", "application/json");
165
166
  return headers;
@@ -367,6 +368,19 @@ function createGscdumpV1Client(options) {
367
368
  createRealtimeTicket: (input, executeOptions) => execute("realtime.tickets.create", input, executeOptions)
368
369
  };
369
370
  }
371
+ function utf8Size(value) {
372
+ let bytes = 0;
373
+ for (let index = 0; index < value.length; index++) {
374
+ const code = value.charCodeAt(index);
375
+ if (code <= 127) bytes++;
376
+ else if (code <= 2047) bytes += 2;
377
+ else if (code >= 55296 && code <= 56319 && index + 1 < value.length && value.charCodeAt(index + 1) >= 56320 && value.charCodeAt(index + 1) <= 57343) {
378
+ bytes += 4;
379
+ index++;
380
+ } else bytes += 3;
381
+ }
382
+ return bytes;
383
+ }
370
384
  const GSCDUMP_REALTIME_V1_SDK_VERSION = "0.37.7";
371
385
  var GscdumpRealtimeV1Error = class extends Error {
372
386
  tag = "GscdumpRealtimeV1Error";
@@ -417,13 +431,14 @@ function defaultRuntime() {
417
431
  now: () => Date.now()
418
432
  };
419
433
  }
420
- function utf8Size(value) {
421
- return new TextEncoder().encode(value).byteLength;
422
- }
434
+ const UTF8_DECODER = new TextDecoder();
435
+ const REALTIME_EVENT_NAMES = new Set(REALTIME_V1_EVENT_NAMES);
436
+ const REALTIME_RESOURCE_TYPES = new Set(REALTIME_V1_RESOURCE_TYPES);
437
+ const APPLIED_EVENT_CACHE_MAX = 1e4;
423
438
  async function messageText(raw) {
424
439
  if (typeof raw === "string") return raw;
425
- if (raw instanceof ArrayBuffer) return new TextDecoder().decode(raw);
426
- if (ArrayBuffer.isView(raw)) return new TextDecoder().decode(new Uint8Array(raw.buffer, raw.byteOffset, raw.byteLength));
440
+ if (raw instanceof ArrayBuffer) return UTF8_DECODER.decode(raw);
441
+ if (ArrayBuffer.isView(raw)) return UTF8_DECODER.decode(new Uint8Array(raw.buffer, raw.byteOffset, raw.byteLength));
427
442
  if (typeof Blob !== "undefined" && raw instanceof Blob) return raw.text();
428
443
  throw new TypeError("Realtime frames must be text or UTF-8 binary data.");
429
444
  }
@@ -472,6 +487,7 @@ function createGscdumpRealtimeV1Client(options) {
472
487
  const eventFailures = /* @__PURE__ */ new Map();
473
488
  const appliedEventIds = /* @__PURE__ */ new Set();
474
489
  const appliedEventOrder = [];
490
+ let appliedEventCursor = 0;
475
491
  function getSnapshot() {
476
492
  return {
477
493
  transport,
@@ -605,23 +621,25 @@ function createGscdumpRealtimeV1Client(options) {
605
621
  const key = eventDedupeKey(stream, id);
606
622
  if (appliedEventIds.has(key)) return;
607
623
  appliedEventIds.add(key);
608
- appliedEventOrder.push(key);
609
- if (appliedEventOrder.length > 1e4) {
610
- const oldest = appliedEventOrder.shift();
624
+ if (appliedEventOrder.length < APPLIED_EVENT_CACHE_MAX) appliedEventOrder.push(key);
625
+ else {
626
+ const oldest = appliedEventOrder[appliedEventCursor];
611
627
  if (oldest) appliedEventIds.delete(oldest);
628
+ appliedEventOrder[appliedEventCursor] = key;
629
+ appliedEventCursor = (appliedEventCursor + 1) % APPLIED_EVENT_CACHE_MAX;
612
630
  }
613
631
  }
614
632
  function eventFailureKey(event) {
615
633
  return `${event.cursor?.streamId ?? "ephemeral"}:${event.id}:${event.cursor?.sequence ?? "ephemeral"}`;
616
634
  }
617
635
  function eventForRequiredEffect(event) {
618
- return REALTIME_V1_EVENT_NAMES.includes(event.name) && event.eventVersion === 1 ? event : {
636
+ return REALTIME_EVENT_NAMES.has(event.name) && event.eventVersion === 1 ? event : {
619
637
  ...event,
620
638
  data: null
621
639
  };
622
640
  }
623
641
  function eventAdvisories(event) {
624
- if (!REALTIME_V1_EVENT_NAMES.includes(event.name)) observe({
642
+ if (!REALTIME_EVENT_NAMES.has(event.name)) observe({
625
643
  type: "advisory",
626
644
  code: "unknown_event",
627
645
  event
@@ -631,7 +649,7 @@ function createGscdumpRealtimeV1Client(options) {
631
649
  code: "unsupported_event_version",
632
650
  event
633
651
  });
634
- if (event.changes.some((change) => !REALTIME_V1_RESOURCE_TYPES.includes(change.type))) observe({
652
+ if (event.changes.some((change) => !REALTIME_RESOURCE_TYPES.has(change.type))) observe({
635
653
  type: "advisory",
636
654
  code: "unknown_resource",
637
655
  event
@@ -1058,7 +1076,8 @@ function createGscdumpRealtimeV1Client(options) {
1058
1076
  context.lastPongAt = runtime.now();
1059
1077
  return;
1060
1078
  }
1061
- if (utf8Size(text) > GSCDUMP_REALTIME_LIMITS.outboundFrameMaxBytes) throw new GscdumpRealtimeV1Error({
1079
+ const textBytes = utf8Size(text);
1080
+ if (textBytes > GSCDUMP_REALTIME_LIMITS.outboundFrameMaxBytes) throw new GscdumpRealtimeV1Error({
1062
1081
  code: "protocol_error",
1063
1082
  message: "A server frame exceeded the v1 outbound frame limit.",
1064
1083
  retryable: false,
@@ -1086,8 +1105,7 @@ function createGscdumpRealtimeV1Client(options) {
1086
1105
  cause: parsed.error
1087
1106
  });
1088
1107
  if (parsed.data.type === "event") {
1089
- const eventLimit = parsed.data.delivery === "durable" ? GSCDUMP_REALTIME_LIMITS.durableEventMaxBytes : GSCDUMP_REALTIME_LIMITS.ephemeralEventMaxBytes;
1090
- if (utf8Size(text) > eventLimit) throw new GscdumpRealtimeV1Error({
1108
+ if (textBytes > (parsed.data.delivery === "durable" ? GSCDUMP_REALTIME_LIMITS.durableEventMaxBytes : GSCDUMP_REALTIME_LIMITS.ephemeralEventMaxBytes)) throw new GscdumpRealtimeV1Error({
1091
1109
  code: "protocol_error",
1092
1110
  message: `A ${parsed.data.delivery} event exceeded its v1 size limit.`,
1093
1111
  retryable: false,
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@gscdump/sdk",
3
3
  "type": "module",
4
- "version": "0.38.2",
4
+ "version": "0.39.0",
5
5
  "description": "Consumer SDK for hosted gscdump.com integrations.",
6
6
  "author": {
7
7
  "name": "Harlan Wilton",
@@ -49,10 +49,10 @@
49
49
  "date-fns": "^4.4.0",
50
50
  "ofetch": "^1.5.1",
51
51
  "zod": "^4.4.3",
52
- "@gscdump/contracts": "0.38.2",
53
- "@gscdump/engine": "0.38.2",
54
- "gscdump": "0.38.2",
55
- "@gscdump/analysis": "0.38.2"
52
+ "@gscdump/contracts": "0.39.0",
53
+ "@gscdump/analysis": "0.39.0",
54
+ "@gscdump/engine": "0.39.0",
55
+ "gscdump": "0.39.0"
56
56
  },
57
57
  "devDependencies": {
58
58
  "typescript": "^6.0.3",