@gscdump/engine-gsc-api 0.33.0 → 0.33.3

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.d.mts CHANGED
@@ -175,11 +175,23 @@ interface RunGscSearchAppearanceContextSliceOptions {
175
175
  searchType: SearchType$1;
176
176
  rowsThisPage: number;
177
177
  }) => void;
178
+ continuation?: SearchAppearanceContinuation;
178
179
  }
180
+ type SearchAppearanceContinuation = {
181
+ phase: 'discovery';
182
+ appearances: string[];
183
+ nextStartRow: number;
184
+ } | {
185
+ phase: 'context';
186
+ appearances: string[];
187
+ appearanceIndex: number;
188
+ nextStartRow: number;
189
+ };
179
190
  interface RunGscSearchAppearanceContextSliceResult {
180
191
  appearances: string[];
181
192
  totalRows: number;
182
193
  hasMore: boolean;
194
+ continuation?: SearchAppearanceContinuation;
183
195
  }
184
196
  declare function runGscSyncSlice(opts: RunGscSyncSliceOptions): Promise<RunGscSyncSliceResult>;
185
197
  /**
package/dist/index.mjs CHANGED
@@ -1,6 +1,6 @@
1
1
  import { googleSearchConsole } from "gscdump/api";
2
2
  import { between, clicks, date, extractMetricFilters, extractSpecialOperatorFilters, gsc, normalizeFilter } from "gscdump/query";
3
- import { assertDimensionsSupported, getDimensionFilters, getFilterDimensions, matchesDimensionFilter, matchesMetricFilter, matchesTopLevelPage, metricValue } from "@gscdump/engine/resolver";
3
+ import { assertDimensionsSupported, getFilterDimensions, matchesDimensionFilter, matchesMetricFilter, matchesTopLevelPage, metricValue } from "@gscdump/engine/resolver";
4
4
  import { buildLogicalPlan } from "gscdump/query/plan";
5
5
  const METRIC_NAMES = [
6
6
  "clicks",
@@ -11,12 +11,20 @@ const METRIC_NAMES = [
11
11
  function isMetricDimension$1(dim) {
12
12
  return METRIC_NAMES.includes(dim);
13
13
  }
14
+ function isLocalDimensionFilter(filter) {
15
+ return filter.dimension !== "date" && !isMetricDimension$1(filter.dimension) && filter.operator !== "topLevel" && !filter.operator.startsWith("metric");
16
+ }
17
+ function matchesDimensionFilterTree(row, filter) {
18
+ if (!filter || !("_filters" in filter)) return true;
19
+ const checks = [...filter._filters.filter(isLocalDimensionFilter).map((f) => matchesDimensionFilter(row, f)), ...(filter._nestedGroups ?? []).map((group) => matchesDimensionFilterTree(row, group))];
20
+ if (checks.length === 0) return true;
21
+ return filter._groupType === "or" ? checks.some(Boolean) : checks.every(Boolean);
22
+ }
14
23
  function applyBuilderStatePostProcessing(rows, state) {
15
- const dimensionFilters = getDimensionFilters(state.filter, isMetricDimension$1);
16
24
  const metricFilters = extractMetricFilters(state.filter);
17
25
  const specialFilters = extractSpecialOperatorFilters(state.filter);
18
26
  const ordered = [...rows.filter((row) => {
19
- if (!dimensionFilters.every((filter) => matchesDimensionFilter(row, filter))) return false;
27
+ if (!matchesDimensionFilterTree(row, state.filter)) return false;
20
28
  if (!metricFilters.every((filter) => matchesMetricFilter(row, filter))) return false;
21
29
  if (specialFilters.some((filter) => filter.operator === "topLevel") && !matchesTopLevelPage(row)) return false;
22
30
  return true;
@@ -49,12 +57,12 @@ async function fetchGscTopN(opts) {
49
57
  const key = row[dimension.dimension];
50
58
  if (typeof key !== "string" || !key) return null;
51
59
  const impressions = Number(row.impressions ?? 0);
52
- const position = Number(row.position ?? 0);
60
+ const position = Math.max(1, Number(row.position ?? 0));
53
61
  return {
54
62
  key,
55
63
  clicks: Number(row.clicks ?? 0),
56
64
  impressions,
57
- sum_position: position * impressions
65
+ sum_position: (position - 1) * impressions
58
66
  };
59
67
  }).filter((x) => x != null);
60
68
  if (!orderByClicksDesc) mapped.sort((a, b) => b.clicks - a.clicks);
@@ -73,7 +81,7 @@ async function fetchGscDaily(opts) {
73
81
  date: Date.parse(`${row.date}T00:00:00Z`),
74
82
  clicks: row.clicks ?? 0,
75
83
  impressions,
76
- sum_position: (row.position ?? 0) * impressions,
84
+ sum_position: (Math.max(1, row.position ?? 0) - 1) * impressions,
77
85
  anonymizedImpressionsPct: 0
78
86
  };
79
87
  }).filter((x) => x != null).sort((a, b) => a.date - b.date);
@@ -105,15 +113,18 @@ function createGscApiQuerySource(options) {
105
113
  buildLogicalPlan(state, GSC_API_CAPABILITIES);
106
114
  const filterDims = getFilterDimensions(state.filter, isMetricDimension);
107
115
  assertDimensionsSupported([...state.dimensions, ...filterDims], "api", "gsc-api query source");
108
- return applyBuilderStatePostProcessing(await collectRows(client.query(siteUrl, builderFromState(state))), state);
116
+ const apiState = {
117
+ ...state,
118
+ rowLimit: void 0,
119
+ startRow: void 0
120
+ };
121
+ return applyBuilderStatePostProcessing(await collectRows(client.query(siteUrl, builderFromState(apiState))), state);
109
122
  }
110
123
  };
111
124
  }
112
125
  const PRO_ONLY_DIMENSIONS = /* @__PURE__ */ new Set(["queryCanonical", "page_keywords"]);
113
126
  function canProxyToGsc(state) {
114
127
  if (state.dimensions.some((d) => PRO_ONLY_DIMENSIONS.has(d))) return false;
115
- if (extractMetricFilters(state.filter).length > 0) return false;
116
- if (extractSpecialOperatorFilters(state.filter).length > 0) return false;
117
128
  return true;
118
129
  }
119
130
  function withSearchType(state, searchType) {
@@ -211,7 +222,7 @@ async function runGscSyncSlice(opts) {
211
222
  const maxPages = opts.maxPages ?? Infinity;
212
223
  const searchType = opts.searchType ?? "web";
213
224
  const dimensions = opts.dimensions ? [...opts.dimensions] : [...DIMENSIONS_BY_TABLE[opts.table]];
214
- const dataState = opts.dataState ?? "all";
225
+ const dataState = opts.dataState ?? (dimensions.includes("hour") ? "hourly_all" : "all");
215
226
  const dimensionFilterGroups = buildDimensionFilterGroups(opts.domainFilter, opts.dimensionFilters);
216
227
  const loopStart = Date.now();
217
228
  let startRow = opts.initialStartRow ?? 0;
@@ -278,7 +289,7 @@ async function runGscSyncSlice(opts) {
278
289
  searchType,
279
290
  rowsThisPage: rows.length
280
291
  });
281
- const isLastPage = rows.length < rowLimit;
292
+ const isLastPage = rows.length === 0;
282
293
  const nextStartRow = page.startRow + rows.length;
283
294
  const prefetch = isLastPage ? null : startFetchIfAllowed(nextStartRow);
284
295
  if (rows.length > 0) {
@@ -323,11 +334,11 @@ function contextTableForGrain(grain) {
323
334
  }
324
335
  async function runGscSearchAppearanceContextSlice(opts) {
325
336
  const table = opts.table ?? contextTableForGrain(opts.grain ?? "page_query");
326
- const appearances = opts.appearances?.slice() ?? [];
337
+ const appearances = opts.continuation?.appearances?.slice() ?? opts.appearances?.slice() ?? [];
327
338
  let totalRows = 0;
328
339
  let hasMore = false;
329
- if (!opts.appearances) {
330
- const discovered = /* @__PURE__ */ new Set();
340
+ if (!opts.appearances && opts.continuation?.phase !== "context") {
341
+ const discovered = new Set(appearances);
331
342
  const discovery = await runGscSyncSlice({
332
343
  client: opts.client,
333
344
  siteUrl: opts.siteUrl,
@@ -340,6 +351,7 @@ async function runGscSearchAppearanceContextSlice(opts) {
340
351
  maxPages: opts.maxPages,
341
352
  cpuBudgetMs: opts.cpuBudgetMs,
342
353
  searchType: opts.searchType,
354
+ initialStartRow: opts.continuation?.phase === "discovery" ? opts.continuation.nextStartRow : void 0,
343
355
  onPage: opts.onPage,
344
356
  onBatch: async (rows) => {
345
357
  for (const row of rows) {
@@ -350,10 +362,23 @@ async function runGscSearchAppearanceContextSlice(opts) {
350
362
  }
351
363
  });
352
364
  totalRows += discovery.totalRows;
365
+ if (discovery.hasMore) return {
366
+ appearances: [...discovered],
367
+ totalRows,
368
+ hasMore: true,
369
+ continuation: {
370
+ phase: "discovery",
371
+ appearances: [...discovered],
372
+ nextStartRow: discovery.nextStartRow
373
+ }
374
+ };
353
375
  hasMore ||= discovery.hasMore;
354
- appearances.push(...discovered);
376
+ appearances.splice(0, appearances.length, ...discovered);
355
377
  }
356
- for (const searchAppearance of appearances) {
378
+ const startIndex = opts.continuation?.phase === "context" ? opts.continuation.appearanceIndex : 0;
379
+ const startRow = opts.continuation?.phase === "context" ? opts.continuation.nextStartRow : 0;
380
+ for (let i = startIndex; i < appearances.length; i++) {
381
+ const searchAppearance = appearances[i];
357
382
  const context = await runGscSyncSlice({
358
383
  client: opts.client,
359
384
  siteUrl: opts.siteUrl,
@@ -370,6 +395,7 @@ async function runGscSearchAppearanceContextSlice(opts) {
370
395
  maxPages: opts.maxPages,
371
396
  cpuBudgetMs: opts.cpuBudgetMs,
372
397
  searchType: opts.searchType,
398
+ initialStartRow: i === startIndex ? startRow : void 0,
373
399
  onPage: opts.onPage,
374
400
  onBatch: (rows) => opts.onContextBatch({
375
401
  searchAppearance,
@@ -378,6 +404,17 @@ async function runGscSearchAppearanceContextSlice(opts) {
378
404
  })
379
405
  });
380
406
  totalRows += context.totalRows;
407
+ if (context.hasMore) return {
408
+ appearances,
409
+ totalRows,
410
+ hasMore: true,
411
+ continuation: {
412
+ phase: "context",
413
+ appearances,
414
+ appearanceIndex: i,
415
+ nextStartRow: context.nextStartRow
416
+ }
417
+ };
381
418
  hasMore ||= context.hasMore;
382
419
  }
383
420
  return {
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@gscdump/engine-gsc-api",
3
3
  "type": "module",
4
- "version": "0.33.0",
4
+ "version": "0.33.3",
5
5
  "description": "GSC live-API engine adapter — wraps the Search Analytics REST API as an AnalysisQuerySource for typed analyzer dispatch.",
6
6
  "author": {
7
7
  "name": "Harlan Wilton",
@@ -36,8 +36,8 @@
36
36
  "node": ">=18"
37
37
  },
38
38
  "dependencies": {
39
- "gscdump": "0.33.0",
40
- "@gscdump/engine": "0.33.0"
39
+ "gscdump": "0.33.3",
40
+ "@gscdump/engine": "0.33.3"
41
41
  },
42
42
  "devDependencies": {
43
43
  "vitest": "^4.1.9"