@gscdump/sdk 3.2.0 → 3.3.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.
@@ -72,7 +72,7 @@ interface ClassifySearchConsoleStageInput {
72
72
  pageMoverDropCount?: number | null;
73
73
  /** v2 trajectory axis — when present, drives the growth override + decline detection. */
74
74
  trajectory?: SearchConsoleStageTrajectory | null;
75
- /** v2 maturity axis — impressions over the trailing 28 days. Gates whether coverage% is even meaningful. */
75
+ /** v2 reach axis — impressions over the trailing 28 days. Gates comparative growth claims. */
76
76
  impressions28d?: number | null;
77
77
  /**
78
78
  * v2 on-page technical faults from the crawl audit (broken links/images,
@@ -92,11 +92,8 @@ interface ClassifySearchConsoleStageInput {
92
92
  siteType?: string | null;
93
93
  }
94
94
  /**
95
- * v2 classifier. Trajectory and maturity are first-class axes that run BEFORE
96
- * the coverage/discovery rungs, so a growing site is told to keep expanding —
97
- * never to "fix indexing". Reuses the existing stage-key enum (growth →
98
- * `healthy_growth_ready`, nascent → `waiting_for_data`, mass crawled-not-indexed
99
- * → `index_rejection`, on-page/crawl faults → `crawl_blocked`).
95
+ * v2 classifier. Direct indexing evidence remains actionable at any traffic
96
+ * volume. Comparative reach claims require enough current observations.
100
97
  */
101
98
  declare function classifySearchConsoleStage(input: ClassifySearchConsoleStageInput): SearchConsoleStage;
102
99
  export { ClassifySearchConsoleStageInput, SearchConsoleStage, SearchConsoleStageEvidence, SearchConsoleStageIssue, SearchConsoleStageKey, SearchConsoleStagePage, SearchConsoleStageSeverity, SearchConsoleStageSitemap, SearchConsoleStageSummary, SearchConsoleStageTrajectory, classifySearchConsoleStage };
@@ -128,7 +128,7 @@ function stage(key, evidence) {
128
128
  };
129
129
  }
130
130
  const ESTABLISHED_IMPRESSIONS_28D = 2e4;
131
- const NASCENT_IMPRESSIONS_28D = 1e3;
131
+ const MIN_GROWTH_IMPRESSIONS_28D = 1e3;
132
132
  const GROWTH_CLICKS_PCT_90D = 10;
133
133
  const GROWTH_IMPRESSIONS_PCT_90D = 20;
134
134
  const DECLINE_CLICKS_PCT = -10;
@@ -272,9 +272,8 @@ function classifySearchConsoleStage(input) {
272
272
  const posDelta90d = traj?.positionDelta90d ?? null;
273
273
  const clicks28d = traj?.clicksPct28d ?? null;
274
274
  const clicksPrior28d = traj?.clicksPrior28d ?? null;
275
- const isGrowing = clicks90d != null && clicks90d > GROWTH_CLICKS_PCT_90D || imp90d != null && imp90d > GROWTH_IMPRESSIONS_PCT_90D && posDelta90d != null && posDelta90d < 0;
275
+ const isGrowing = impressions28d != null && impressions28d >= MIN_GROWTH_IMPRESSIONS_28D && (clicks90d != null && clicks90d > GROWTH_CLICKS_PCT_90D || imp90d != null && imp90d > GROWTH_IMPRESSIONS_PCT_90D && posDelta90d != null && posDelta90d < 0);
276
276
  const isDeclining = clicks90d != null && clicks90d < DECLINE_CLICKS_PCT && clicks28d != null && clicks28d < DECLINE_CLICKS_PCT && clicksPrior28d != null && clicksPrior28d >= MIN_DECLINE_PRIOR_CLICKS;
277
- const isNascent = impressions28d != null && impressions28d < NASCENT_IMPRESSIONS_28D;
278
277
  const isEstablished = impressions28d != null && impressions28d >= ESTABLISHED_IMPRESSIONS_28D;
279
278
  if (hardBlocks > Math.max(10, totalUrls * .05)) return stage("crawl_blocked", [{
280
279
  label: "Crawl / on-page faults",
@@ -285,7 +284,7 @@ function classifySearchConsoleStage(input) {
285
284
  value: `${formatSearchConsoleCount(indexed)} of ${formatSearchConsoleCount(totalUrls)}`,
286
285
  source: "indexing"
287
286
  }]);
288
- const severeCoverage = isNascent ? null : severeCoverageStage(input, totalUrls, indexed);
287
+ const severeCoverage = severeCoverageStage(input, totalUrls, indexed);
289
288
  if (severeCoverage) return severeCoverage;
290
289
  if (isDeclining) return stage("declining_visibility", [{
291
290
  label: "Clicks 90d",
@@ -332,15 +331,6 @@ function classifySearchConsoleStage(input) {
332
331
  value: formatSearchConsoleCount(notIndexed),
333
332
  source: "indexing"
334
333
  }]);
335
- if (isNascent) return stage("waiting_for_data", [{
336
- label: "Impressions (28d)",
337
- value: formatSearchConsoleCount(impressions28d ?? 0),
338
- source: "performance"
339
- }, {
340
- label: "Indexed pages",
341
- value: `${formatSearchConsoleCount(indexed)} of ${formatSearchConsoleCount(totalUrls)}`,
342
- source: "indexing"
343
- }]);
344
334
  if (!isEstablished && (sitemaps.length === 0 || sitemapErrors > 0 || unknown > Math.max(5, totalUrls * .1))) return stage("weak_discovery", [{
345
335
  label: "Sitemaps",
346
336
  value: sitemaps.length === 0 ? "None registered" : `${formatSearchConsoleCount(sitemapErrors)} errors`,
@@ -1,5 +1,5 @@
1
1
  import { SearchConsoleStageIssue } from "./search-console-stage.mjs";
2
- type ReachStage = 'waiting_for_data' | 'emerging' | 'growing' | 'plateaued' | 'declining' | 'faded' | 'decayed';
2
+ type ReachStage = 'emerging' | 'growing' | 'plateaued' | 'declining' | 'faded' | 'decayed';
3
3
  type HealthStage = 'healthy' | 'crawl_faults' | 'quality_rejection';
4
4
  interface TriageEvidence {
5
5
  label: string;
@@ -9,7 +9,7 @@ interface TriageEvidence {
9
9
  * Distance-to-next-stage for one axis, so the UI never re-derives thresholds.
10
10
  *
11
11
  * `direction`:
12
- * - `advance` — up-path rung (waiting → emerging → growing). `pct` = value/target.
12
+ * - `advance` — up-path rung (emerging → growing). `pct` = value/target.
13
13
  * - `escape` — off-ramp recovery (declining/faded/decayed) or a held health gate
14
14
  * (crawl_faults/quality_rejection). `pct` = inverse distance: closer to passing ⇒ higher.
15
15
  * - `sustain` — already good (growing/healthy). `nextStage` null, `pct` 1, framed as momentum.
@@ -46,7 +46,6 @@ interface SiteTriage {
46
46
  headline: 'reach' | 'health';
47
47
  }
48
48
  interface SiteTriageInput {
49
- connected: boolean;
50
49
  /** Impressions over the trailing 28 days — the maturity tier. */
51
50
  impressions28d?: number | null;
52
51
  /** Lifetime-ish impressions (trailing 12 months) — separates new from decayed/faded. */
@@ -1,8 +1,8 @@
1
1
  import { countSearchConsoleIssues, formatSearchConsoleCount } from "./search-console-signals.mjs";
2
2
  import { normalizeSiteType } from "./site-baseline.mjs";
3
- const NASCENT_IMPRESSIONS_28D = 1e3;
3
+ const MIN_GROWTH_IMPRESSIONS_28D = 1e3;
4
4
  const ESTABLISHED_IMPRESSIONS_28D = 2e4;
5
- const LIFETIME_FLOOR = 500;
5
+ const MIN_HISTORY_IMPRESSIONS = 500;
6
6
  const GROWTH_CLICKS_PCT = 10;
7
7
  const GROWTH_IMPRESSIONS_PCT = 20;
8
8
  const DECLINE_CLICKS_PCT = -10;
@@ -148,13 +148,9 @@ function classifyHealthStage(input) {
148
148
  };
149
149
  }
150
150
  const REACH_COPY = {
151
- waiting_for_data: {
152
- summary: "Too new to diagnose — not enough search history yet.",
153
- primaryAction: "Submit a clean sitemap, add internal links, and give it time."
154
- },
155
151
  emerging: {
156
- summary: "Early but climbing real impressions are starting to land.",
157
- primaryAction: "Keep publishing on the themes already gaining impressions."
152
+ summary: "Search Console is ready. Search visibility is still limited.",
153
+ primaryAction: "Improve discovery and indexing, then build on pages that earn impressions."
158
154
  },
159
155
  growing: {
160
156
  summary: "Discoverable, indexed-enough, and trending up. The next work is expansion, not cleanup.",
@@ -195,12 +191,8 @@ function classifyReachStage(input) {
195
191
  const imp90dPct = input.impressionsPct90d ?? null;
196
192
  const posDelta = input.positionDelta90d ?? null;
197
193
  const liveness = input.livenessRatio ?? null;
198
- if (imp12m != null && imp12m < LIFETIME_FLOOR) return reach("waiting_for_data", [{
199
- label: "Impressions (12m)",
200
- value: formatSearchConsoleCount(imp12m)
201
- }], advance("emerging", "lifetime impressions", imp12m, LIFETIME_FLOOR, `${formatSearchConsoleCount(imp12m)} of ${formatSearchConsoleCount(LIFETIME_FLOOR)} lifetime impressions — collecting data, keep indexing`));
202
- const hadRealReach = (imp12m ?? imp28d) > LIFETIME_FLOOR;
203
- const isGrowing = clicks90dPct != null && clicks90dPct > GROWTH_CLICKS_PCT || imp90dPct != null && imp90dPct > GROWTH_IMPRESSIONS_PCT && posDelta != null && posDelta < 0;
194
+ const hadRealReach = (imp12m ?? imp28d) > MIN_HISTORY_IMPRESSIONS;
195
+ const isGrowing = imp28d >= MIN_GROWTH_IMPRESSIONS_28D && (clicks90dPct != null && clicks90dPct > GROWTH_CLICKS_PCT || imp90dPct != null && imp90dPct > GROWTH_IMPRESSIONS_PCT && posDelta != null && posDelta < 0);
204
196
  if (hadRealReach && liveness != null && liveness < FADED_LIVENESS && !isGrowing) return reach("faded", [{
205
197
  label: "Recent week vs peak",
206
198
  value: `${(liveness * 100).toFixed(0)}%`
@@ -234,7 +226,7 @@ function classifyReachStage(input) {
234
226
  }] : []], sustain(growthMetric, growthPct, growthLabel));
235
227
  }
236
228
  const ctr = clicks28d != null && imp28d > 0 ? clicks28d / imp28d : null;
237
- if (hadRealReach && imp28d >= NASCENT_IMPRESSIONS_28D && ctr != null && ctr < DECAY_CTR) return reach("decayed", [{
229
+ if (hadRealReach && imp28d >= MIN_GROWTH_IMPRESSIONS_28D && ctr != null && ctr < DECAY_CTR) return reach("decayed", [{
238
230
  label: "Impressions (28d)",
239
231
  value: formatSearchConsoleCount(imp28d)
240
232
  }, {
@@ -248,10 +240,10 @@ function classifyReachStage(input) {
248
240
  label: "Impressions (28d)",
249
241
  value: formatSearchConsoleCount(imp28d)
250
242
  }], growthProgression("plateaued"));
251
- if (imp28d < NASCENT_IMPRESSIONS_28D) return reach("emerging", [{
243
+ if (imp28d < MIN_GROWTH_IMPRESSIONS_28D) return reach("emerging", [{
252
244
  label: "Impressions (28d)",
253
245
  value: formatSearchConsoleCount(imp28d)
254
- }], growthProgression("emerging"));
246
+ }], advance("growing", "28d impressions", imp28d, MIN_GROWTH_IMPRESSIONS_28D, `${formatSearchConsoleCount(imp28d)} impressions in 28 days. Build enough visibility to measure growth reliably.`));
255
247
  return reach("plateaued", [{
256
248
  label: "Impressions (28d)",
257
249
  value: formatSearchConsoleCount(imp28d)
@@ -1,6 +1,6 @@
1
1
  import { RealtimeV1Cursor, RealtimeV1Event, RealtimeV1StreamHead, RealtimeV1StreamId } from "@gscdump/contracts/v1/realtime";
2
2
  type MaybePromise<T> = T | Promise<T>;
3
- declare const GSCDUMP_REALTIME_V1_SDK_VERSION: "3.2.0";
3
+ declare const GSCDUMP_REALTIME_V1_SDK_VERSION: "3.3.0";
4
4
  type GscdumpRealtimeV1TransportState = 'idle' | 'ticketing' | 'connecting' | 'handshaking' | 'replaying' | 'live' | 'waiting' | 'stopped' | 'terminal';
5
5
  type GscdumpRealtimeV1Freshness = 'unknown' | 'stale' | 'applying' | 'fresh' | 'resyncing' | 'degraded';
6
6
  type GscdumpRealtimeV1ErrorCode = 'cursor_store_failed' | 'effect_failed' | 'heartbeat_stale' | 'integration_failed' | 'protocol_error' | 'resync_failed' | 'runtime_unavailable' | 'socket_error' | 'ticket_invalid' | 'ticket_provider_failed' | 'upgrade_rejected';
@@ -1,6 +1,6 @@
1
1
  import { utf8Size } from "../utf8.mjs";
2
2
  import { GSCDUMP_REALTIME_ACK_POLICY, GSCDUMP_REALTIME_CLOSE_CODES, GSCDUMP_REALTIME_CONNECTION_POLICY, GSCDUMP_REALTIME_LIMITS, GSCDUMP_REALTIME_PING, GSCDUMP_REALTIME_PONG, GSCDUMP_REALTIME_PROTOCOL_VERSION, GSCDUMP_REALTIME_SUBPROTOCOL, REALTIME_V1_EVENT_NAMES, REALTIME_V1_RESOURCE_TYPES, createRealtimeV1Schemas } from "@gscdump/contracts/v1/realtime";
3
- const GSCDUMP_REALTIME_V1_SDK_VERSION = "3.2.0";
3
+ const GSCDUMP_REALTIME_V1_SDK_VERSION = "3.3.0";
4
4
  var GscdumpRealtimeV1Error = class extends Error {
5
5
  tag = "GscdumpRealtimeV1Error";
6
6
  code;
@@ -87,7 +87,7 @@ function createGscdumpRealtimeV1Client(options) {
87
87
  const schemas = createRealtimeV1Schemas();
88
88
  const runtime = options.runtime ?? defaultRuntime();
89
89
  const cursorStore = options.cursorStore ?? createMemoryCursorStore();
90
- const sdkVersion = options.sdkVersion ?? "3.2.0";
90
+ const sdkVersion = options.sdkVersion ?? "3.3.0";
91
91
  if (!sdkVersion) throw new TypeError("sdkVersion cannot be empty.");
92
92
  let running = false;
93
93
  let epoch = 0;
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@gscdump/sdk",
3
3
  "type": "module",
4
- "version": "3.2.0",
4
+ "version": "3.3.0",
5
5
  "description": "Consumer SDK for hosted gscdump.com integrations.",
6
6
  "author": {
7
7
  "name": "Harlan Wilton",
@@ -144,14 +144,14 @@
144
144
  "node": ">=22"
145
145
  },
146
146
  "dependencies": {
147
+ "@gscdump/contracts": "^3.3.0",
148
+ "gscdump": "^3.3.0",
147
149
  "ofetch": "^1.5.1",
148
- "zod": "^4.4.3",
149
- "@gscdump/contracts": "^3.2.0",
150
- "gscdump": "^3.2.0"
150
+ "zod": "^4.4.3"
151
151
  },
152
152
  "devDependencies": {
153
153
  "typescript": "^7.0.2",
154
- "vitest": "^4.1.10"
154
+ "vitest": "^4.1.11"
155
155
  },
156
156
  "scripts": {
157
157
  "build": "obuild",