@gscdump/engine-gsc-api 1.3.2 → 1.4.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.
package/dist/index.mjs CHANGED
@@ -1,555 +1,5 @@
1
- import { googleSearchConsole } from "gscdump";
2
- import { assertDimensionsSupported, dimensionValue, getFilterDimensions, matchesTopLevelPage, metricValue } from "@gscdump/engine/resolver";
3
- import { between, clicks, date, extractMetricFilters, extractSpecialOperatorFilters, gsc } from "gscdump/query";
4
- import { buildLogicalPlan } from "gscdump/query/plan";
5
- import { normalizeUrl } from "gscdump/normalize";
6
- const METRIC_NAMES = [
7
- "clicks",
8
- "impressions",
9
- "ctr",
10
- "position"
11
- ];
12
- function isMetricDimension$1(dim) {
13
- return METRIC_NAMES.includes(dim);
14
- }
15
- function isLocalDimensionFilter(filter) {
16
- return filter.dimension !== "date" && !isMetricDimension$1(filter.dimension) && filter.operator !== "topLevel" && !filter.operator.startsWith("metric");
17
- }
18
- function compileDimensionFilter(filter) {
19
- const { dimension, expression } = filter;
20
- switch (filter.operator) {
21
- case "equals": return dimension === "page" ? (row) => normalizeUrl(dimensionValue(row, dimension)) === expression : (row) => dimensionValue(row, dimension) === expression;
22
- case "notEquals": return dimension === "page" ? (row) => normalizeUrl(dimensionValue(row, dimension)) !== expression : (row) => dimensionValue(row, dimension) !== expression;
23
- case "contains": return (row) => dimensionValue(row, dimension).includes(expression);
24
- case "notContains": return (row) => !dimensionValue(row, dimension).includes(expression);
25
- case "includingRegex": {
26
- let regex;
27
- return (row) => (regex ??= new RegExp(expression)).test(dimensionValue(row, dimension));
28
- }
29
- case "excludingRegex": {
30
- let regex;
31
- return (row) => !(regex ??= new RegExp(expression)).test(dimensionValue(row, dimension));
32
- }
33
- default: return () => true;
34
- }
35
- }
36
- function compileMetricFilter(filter) {
37
- const { dimension } = filter;
38
- const target = Number(filter.expression);
39
- switch (filter.operator) {
40
- case "metricGte": return (row) => metricValue(row, dimension) >= target;
41
- case "metricGt": return (row) => metricValue(row, dimension) > target;
42
- case "metricLte": return (row) => metricValue(row, dimension) <= target;
43
- case "metricLt": return (row) => metricValue(row, dimension) < target;
44
- case "metricBetween": {
45
- const target2 = Number(filter.expression2);
46
- return (row) => {
47
- const value = metricValue(row, dimension);
48
- return value >= target && value <= target2;
49
- };
50
- }
51
- default: return () => true;
52
- }
53
- }
54
- function compileDimensionFilterTree(filter) {
55
- if (!filter || !("_filters" in filter)) return () => true;
56
- const localFilters = filter._filters;
57
- const matchers = [];
58
- for (const localFilter of localFilters) if (isLocalDimensionFilter(localFilter)) matchers.push(compileDimensionFilter(localFilter));
59
- for (const group of filter._nestedGroups ?? []) matchers.push(compileDimensionFilterTree(group));
60
- if (matchers.length === 0) return () => true;
61
- if (filter._groupType === "or") return (row) => {
62
- for (const matches of matchers) if (matches(row)) return true;
63
- return false;
64
- };
65
- return (row) => {
66
- for (const matches of matchers) if (!matches(row)) return false;
67
- return true;
68
- };
69
- }
70
- function selectTopK(rows, k, compareRows) {
71
- const heap = [];
72
- const compare = (a, b) => compareRows(a.row, b.row) || a.index - b.index;
73
- const siftUp = (start) => {
74
- let child = start;
75
- while (child > 0) {
76
- const parent = child - 1 >>> 1;
77
- if (compare(heap[parent], heap[child]) >= 0) break;
78
- const swap = heap[parent];
79
- heap[parent] = heap[child];
80
- heap[child] = swap;
81
- child = parent;
82
- }
83
- };
84
- const siftDown = () => {
85
- let parent = 0;
86
- while (true) {
87
- const left = parent * 2 + 1;
88
- if (left >= heap.length) return;
89
- const right = left + 1;
90
- const worse = right < heap.length && compare(heap[right], heap[left]) > 0 ? right : left;
91
- if (compare(heap[parent], heap[worse]) >= 0) return;
92
- const swap = heap[parent];
93
- heap[parent] = heap[worse];
94
- heap[worse] = swap;
95
- parent = worse;
96
- }
97
- };
98
- for (let index = 0; index < rows.length; index++) {
99
- const candidate = {
100
- row: rows[index],
101
- index
102
- };
103
- if (heap.length < k) {
104
- heap.push(candidate);
105
- siftUp(heap.length - 1);
106
- } else if (compare(candidate, heap[0]) < 0) {
107
- heap[0] = candidate;
108
- siftDown();
109
- }
110
- }
111
- heap.sort(compare);
112
- return heap.map((entry) => entry.row);
113
- }
114
- function applyBuilderStatePostProcessing(rows, state) {
115
- const matchesDimensions = compileDimensionFilterTree(state.filter);
116
- const metricMatchers = extractMetricFilters(state.filter).map(compileMetricFilter);
117
- const hasTopLevelFilter = extractSpecialOperatorFilters(state.filter).some((filter) => filter.operator === "topLevel");
118
- const column = state.orderBy?.column ?? "clicks";
119
- const dir = state.orderBy?.dir ?? "desc";
120
- const checkFiniteOrderValues = column !== "date" && state.rowLimit !== void 0;
121
- let hasNonFiniteOrderValue = false;
122
- const filtered = [];
123
- for (const row of rows) {
124
- if (!matchesDimensions(row)) continue;
125
- let matchesMetrics = true;
126
- for (const matchesMetric of metricMatchers) if (!matchesMetric(row)) {
127
- matchesMetrics = false;
128
- break;
129
- }
130
- if (!matchesMetrics) continue;
131
- if (hasTopLevelFilter && !matchesTopLevelPage(row)) continue;
132
- filtered.push(row);
133
- if (checkFiniteOrderValues && !Number.isFinite(metricValue(row, column))) hasNonFiniteOrderValue = true;
134
- }
135
- const compareRows = (a, b) => {
136
- const left = column === "date" ? String(a.date ?? "") : metricValue(a, column);
137
- const right = column === "date" ? String(b.date ?? "") : metricValue(b, column);
138
- if (left === right) return 0;
139
- if (dir === "asc") return left < right ? -1 : 1;
140
- return left > right ? -1 : 1;
141
- };
142
- const offset = Math.max(0, Number(state.startRow ?? 0));
143
- const limit = Math.max(0, Number((state.rowLimit ?? filtered.length) || 0));
144
- if (limit === 0) return [];
145
- const requested = offset + limit;
146
- return (Number.isInteger(requested) && requested > 0 && requested < filtered.length / 2 && !hasNonFiniteOrderValue ? selectTopK(filtered, requested, compareRows) : filtered.sort(compareRows)).slice(offset, offset + limit);
147
- }
148
- async function collectRows(gen) {
149
- const out = [];
150
- for await (const batch of gen) out.push(...batch);
151
- return out;
152
- }
153
- async function fetchGscTopN(opts) {
154
- const { client, siteUrl, dimension, range, orderByClicksDesc, limit, sliceTop, searchType } = opts;
155
- let builder = gsc.select(dimension).where(between(date, range.start, range.end));
156
- if (searchType) builder = builder.type(searchType);
157
- if (orderByClicksDesc) builder = builder.orderBy(clicks, "desc");
158
- if (typeof limit === "number") builder = builder.limit(limit);
159
- const rows = await collectRows(client.query(siteUrl, builder));
160
- const mapRow = (raw) => {
161
- const row = raw;
162
- const key = row[dimension.dimension];
163
- if (typeof key !== "string" || !key) return null;
164
- const impressions = Number(row.impressions ?? 0);
165
- const position = Math.max(1, Number(row.position ?? 0));
166
- return {
167
- key,
168
- clicks: Number(row.clicks ?? 0),
169
- impressions,
170
- sum_position: (position - 1) * impressions
171
- };
172
- };
173
- const topLimit = typeof sliceTop === "number" && Number.isInteger(sliceTop) && sliceTop >= 0 ? sliceTop : void 0;
174
- const mapped = [];
175
- if (topLimit === 0) return mapped;
176
- if (orderByClicksDesc && topLimit !== void 0) {
177
- for (const row of rows) {
178
- const value = mapRow(row);
179
- if (value) mapped.push(value);
180
- if (mapped.length >= topLimit) break;
181
- }
182
- return mapped;
183
- }
184
- if (!orderByClicksDesc && topLimit !== void 0 && topLimit <= 128) {
185
- let requiresFullSort = false;
186
- for (const row of rows) {
187
- const value = mapRow(row);
188
- if (!value) continue;
189
- if (!Number.isFinite(value.clicks)) {
190
- requiresFullSort = true;
191
- break;
192
- }
193
- let insertAt = 0;
194
- while (insertAt < mapped.length && mapped[insertAt].clicks >= value.clicks) insertAt++;
195
- if (insertAt < topLimit) {
196
- mapped.splice(insertAt, 0, value);
197
- if (mapped.length > topLimit) mapped.pop();
198
- }
199
- }
200
- if (!requiresFullSort) return mapped;
201
- mapped.length = 0;
202
- }
203
- for (const row of rows) {
204
- const value = mapRow(row);
205
- if (value) mapped.push(value);
206
- }
207
- if (!orderByClicksDesc) mapped.sort((a, b) => b.clicks - a.clicks);
208
- return typeof sliceTop === "number" ? mapped.slice(0, sliceTop) : mapped;
209
- }
210
- async function fetchGscDaily(opts) {
211
- const { client, siteUrl, range, searchType } = opts;
212
- let builder = gsc.select(date).where(between(date, range.start, range.end));
213
- if (searchType) builder = builder.type(searchType);
214
- const query = builder;
215
- return (await collectRows(client.query(siteUrl, query))).map((r) => {
216
- const row = r;
217
- if (!row.date) return null;
218
- const impressions = row.impressions ?? 0;
219
- return {
220
- date: Date.parse(`${row.date}T00:00:00Z`),
221
- clicks: row.clicks ?? 0,
222
- impressions,
223
- sum_position: (Math.max(1, row.position ?? 0) - 1) * impressions,
224
- anonymizedImpressionsPct: 0
225
- };
226
- }).filter((x) => x != null).sort((a, b) => a.date - b.date);
227
- }
228
- const GSC_API_CAPABILITIES = {
229
- regex: true,
230
- multiDataset: false,
231
- comparisonJoin: false,
232
- windowTotals: false
233
- };
234
- function isMetricDimension(dim) {
235
- return [
236
- "clicks",
237
- "impressions",
238
- "ctr",
239
- "position"
240
- ].includes(dim);
241
- }
242
- function builderFromState(state) {
243
- return { getState: () => state };
244
- }
245
- function canPushDownPagination(state) {
246
- return !state.orderBy && extractMetricFilters(state.filter).length === 0 && extractSpecialOperatorFilters(state.filter).length === 0;
247
- }
248
- function createGscApiQuerySource(options) {
249
- const { client, siteUrl } = options;
250
- return {
251
- name: "gsc-api",
252
- kind: "live",
253
- capabilities: GSC_API_CAPABILITIES,
254
- async queryRows(state) {
255
- buildLogicalPlan(state, GSC_API_CAPABILITIES);
256
- const filterDims = getFilterDimensions(state.filter, isMetricDimension);
257
- assertDimensionsSupported([...state.dimensions, ...filterDims], "api", "gsc-api query source");
258
- const pushDownPagination = canPushDownPagination(state);
259
- const apiState = pushDownPagination ? state : {
260
- ...state,
261
- rowLimit: void 0,
262
- startRow: void 0
263
- };
264
- return applyBuilderStatePostProcessing(await collectRows(client.query(siteUrl, builderFromState(apiState))), pushDownPagination ? {
265
- ...state,
266
- rowLimit: void 0,
267
- startRow: void 0
268
- } : state);
269
- }
270
- };
271
- }
272
- const PRO_ONLY_DIMENSIONS = /* @__PURE__ */ new Set(["queryCanonical", "page_keywords"]);
273
- function canProxyToGsc(state) {
274
- if (state.dimensions.some((d) => PRO_ONLY_DIMENSIONS.has(d))) return false;
275
- return true;
276
- }
277
- function withSearchType(state, searchType) {
278
- return state.searchType ? state : {
279
- ...state,
280
- searchType
281
- };
282
- }
283
- function createLiveGscSource(opts) {
284
- let clientPromise = null;
285
- function getClient() {
286
- if (!clientPromise) clientPromise = opts.getAccessToken().then((accessToken) => opts.createClient?.(accessToken) ?? googleSearchConsole({ accessToken }));
287
- return clientPromise;
288
- }
289
- return {
290
- name: "gsc-api",
291
- kind: "live",
292
- capabilities: {
293
- regex: true,
294
- multiDataset: false,
295
- comparisonJoin: false,
296
- windowTotals: false
297
- },
298
- async queryRows(state) {
299
- const client = await getClient();
300
- const scopedState = opts.searchType !== void 0 ? withSearchType(state, opts.searchType) : state;
301
- return createGscApiQuerySource({
302
- client,
303
- siteUrl: opts.siteUrl
304
- }).queryRows(scopedState);
305
- }
306
- };
307
- }
308
- const DIMENSIONS_BY_TABLE = {
309
- pages: ["page", "date"],
310
- queries: ["query", "date"],
311
- countries: ["country", "date"],
312
- dates: ["device", "date"],
313
- page_queries: [
314
- "page",
315
- "query",
316
- "date"
317
- ],
318
- search_appearance: ["searchAppearance"],
319
- search_appearance_pages: ["page", "date"],
320
- search_appearance_queries: ["query", "date"],
321
- search_appearance_page_queries: [
322
- "page",
323
- "query",
324
- "date"
325
- ],
326
- hourly_pages: ["hour", "page"]
327
- };
328
- function isTimeoutLike(err) {
329
- if (!(err instanceof Error)) return false;
330
- return err.name === "AbortError" || err.message?.includes("timeout") || err.message?.includes("aborted");
331
- }
332
- function buildDimensionFilterGroups(domainFilter, filters = []) {
333
- const out = filters.map((f) => ({
334
- dimension: f.dimension,
335
- operator: f.operator ?? "equals",
336
- expression: f.expression
337
- }));
338
- if (domainFilter?.domain) {
339
- const pattern = `^https?://(www\\.)?${domainFilter.domain.replace(/^www\./, "").replace(/\./g, "\\.")}/`;
340
- out.push({
341
- dimension: "page",
342
- operator: "includingRegex",
343
- expression: pattern
344
- });
345
- }
346
- return out.length > 0 ? [{ filters: out }] : void 0;
347
- }
348
- async function runGscSyncSlice(opts) {
349
- const rowLimit = opts.rowLimit ?? 500;
350
- const cpuBudgetMs = opts.cpuBudgetMs ?? 2e4;
351
- const maxPages = opts.maxPages ?? Infinity;
352
- const searchType = opts.searchType ?? "web";
353
- const dimensions = opts.dimensions ? [...opts.dimensions] : [...DIMENSIONS_BY_TABLE[opts.table]];
354
- const dataState = opts.dataState ?? (dimensions.includes("hour") ? "hourly_all" : "all");
355
- const dimensionFilterGroups = buildDimensionFilterGroups(opts.domainFilter, opts.dimensionFilters);
356
- const loopStart = Date.now();
357
- let startRow = opts.initialStartRow ?? 0;
358
- let totalRows = 0;
359
- let pageCount = 0;
360
- let metadata;
361
- const fetchPage = async (row) => {
362
- const query = {
363
- startDate: opts.startDate,
364
- endDate: opts.endDate,
365
- dimensions,
366
- rowLimit,
367
- startRow: row,
368
- dataState,
369
- type: searchType,
370
- ...dimensionFilterGroups ? { dimensionFilterGroups } : {}
371
- };
372
- try {
373
- const response = await opts.client.searchAnalytics.query(opts.siteUrl, query);
374
- return {
375
- kind: "ok",
376
- startRow: row,
377
- rows: response.rows ?? [],
378
- metadata: response.metadata
379
- };
380
- } catch (err) {
381
- if (isTimeoutLike(err)) return {
382
- kind: "timeout",
383
- startRow: row
384
- };
385
- return {
386
- kind: "error",
387
- error: err
388
- };
389
- }
390
- };
391
- const startFetchIfAllowed = (row) => {
392
- if (pageCount >= maxPages) return null;
393
- if (Date.now() - loopStart >= cpuBudgetMs) return null;
394
- return fetchPage(row);
395
- };
396
- let pending = startFetchIfAllowed(startRow);
397
- if (pending === null) return {
398
- totalRows,
399
- hasMore: true,
400
- nextStartRow: startRow,
401
- metadata
402
- };
403
- while (pending !== null) {
404
- const page = await pending;
405
- pending = null;
406
- if (page.kind === "error") throw page.error;
407
- if (page.kind === "timeout") return {
408
- totalRows,
409
- hasMore: true,
410
- nextStartRow: page.startRow,
411
- metadata
412
- };
413
- const rows = page.rows;
414
- totalRows += rows.length;
415
- pageCount++;
416
- if (page.metadata) metadata = page.metadata;
417
- opts.onPage?.({
418
- searchType,
419
- rowsThisPage: rows.length
420
- });
421
- const isLastPage = rows.length === 0;
422
- const nextStartRow = page.startRow + rows.length;
423
- const prefetch = isLastPage ? null : startFetchIfAllowed(nextStartRow);
424
- if (rows.length > 0) {
425
- if (await opts.onBatch(rows).then(() => false).catch((err) => {
426
- if (isTimeoutLike(err)) return true;
427
- throw err;
428
- })) return {
429
- totalRows: totalRows - rows.length,
430
- hasMore: true,
431
- nextStartRow: page.startRow,
432
- metadata
433
- };
434
- }
435
- if (isLastPage) return {
436
- totalRows,
437
- hasMore: false,
438
- nextStartRow,
439
- metadata
440
- };
441
- if (prefetch === null) return {
442
- totalRows,
443
- hasMore: true,
444
- nextStartRow,
445
- metadata
446
- };
447
- startRow = nextStartRow;
448
- pending = prefetch;
449
- }
450
- return {
451
- totalRows,
452
- hasMore: false,
453
- nextStartRow: startRow,
454
- metadata
455
- };
456
- }
457
- function contextTableForGrain(grain) {
458
- switch (grain) {
459
- case "page": return "search_appearance_pages";
460
- case "query": return "search_appearance_queries";
461
- case "page_query": return "search_appearance_page_queries";
462
- }
463
- }
464
- async function runGscSearchAppearanceContextSlice(opts) {
465
- const table = opts.table ?? contextTableForGrain(opts.grain ?? "page_query");
466
- const appearances = opts.continuation?.appearances?.slice() ?? opts.appearances?.slice() ?? [];
467
- let totalRows = 0;
468
- let hasMore = false;
469
- if (!opts.appearances && opts.continuation?.phase !== "context") {
470
- const discovered = new Set(appearances);
471
- const discovery = await runGscSyncSlice({
472
- client: opts.client,
473
- siteUrl: opts.siteUrl,
474
- table: "search_appearance",
475
- startDate: opts.startDate,
476
- endDate: opts.endDate,
477
- domainFilter: opts.domainFilter,
478
- dataState: opts.dataState,
479
- rowLimit: opts.rowLimit,
480
- maxPages: opts.maxPages,
481
- cpuBudgetMs: opts.cpuBudgetMs,
482
- searchType: opts.searchType,
483
- initialStartRow: opts.continuation?.phase === "discovery" ? opts.continuation.nextStartRow : void 0,
484
- onPage: opts.onPage,
485
- onBatch: async (rows) => {
486
- for (const row of rows) {
487
- const value = String(row.keys?.[0] ?? "");
488
- if (value) discovered.add(value);
489
- }
490
- await opts.onTotalBatch?.(rows);
491
- }
492
- });
493
- totalRows += discovery.totalRows;
494
- if (discovery.hasMore) return {
495
- appearances: [...discovered],
496
- totalRows,
497
- hasMore: true,
498
- continuation: {
499
- phase: "discovery",
500
- appearances: [...discovered],
501
- nextStartRow: discovery.nextStartRow
502
- }
503
- };
504
- hasMore ||= discovery.hasMore;
505
- appearances.splice(0, appearances.length, ...discovered);
506
- }
507
- const startIndex = opts.continuation?.phase === "context" ? opts.continuation.appearanceIndex : 0;
508
- const startRow = opts.continuation?.phase === "context" ? opts.continuation.nextStartRow : 0;
509
- for (let i = startIndex; i < appearances.length; i++) {
510
- const searchAppearance = appearances[i];
511
- const context = await runGscSyncSlice({
512
- client: opts.client,
513
- siteUrl: opts.siteUrl,
514
- table,
515
- startDate: opts.startDate,
516
- endDate: opts.endDate,
517
- domainFilter: opts.domainFilter,
518
- dimensionFilters: [{
519
- dimension: "searchAppearance",
520
- expression: searchAppearance
521
- }],
522
- dataState: opts.dataState,
523
- rowLimit: opts.rowLimit,
524
- maxPages: opts.maxPages,
525
- cpuBudgetMs: opts.cpuBudgetMs,
526
- searchType: opts.searchType,
527
- initialStartRow: i === startIndex ? startRow : void 0,
528
- onPage: opts.onPage,
529
- onBatch: (rows) => opts.onContextBatch({
530
- searchAppearance,
531
- table,
532
- rows
533
- })
534
- });
535
- totalRows += context.totalRows;
536
- if (context.hasMore) return {
537
- appearances,
538
- totalRows,
539
- hasMore: true,
540
- continuation: {
541
- phase: "context",
542
- appearances,
543
- appearanceIndex: i,
544
- nextStartRow: context.nextStartRow
545
- }
546
- };
547
- hasMore ||= context.hasMore;
548
- }
549
- return {
550
- appearances,
551
- totalRows,
552
- hasMore
553
- };
554
- }
1
+ import { fetchGscDaily, fetchGscTopN } from "./rollup-synth.mjs";
2
+ import { createGscApiQuerySource } from "./source.mjs";
3
+ import { canProxyToGsc, createLiveGscSource } from "./live.mjs";
4
+ import { runGscSearchAppearanceContextSlice, runGscSyncSlice } from "./sync-slice.mjs";
555
5
  export { canProxyToGsc, createGscApiQuerySource, createLiveGscSource, fetchGscDaily, fetchGscTopN, runGscSearchAppearanceContextSlice, runGscSyncSlice };
@@ -0,0 +1,25 @@
1
+ import { GoogleSearchConsoleClient } from "gscdump";
2
+ import { BuilderState } from "gscdump/query";
3
+ import { SearchType as SearchType$1 } from "@gscdump/engine";
4
+ import { AnalysisQuerySource } from "@gscdump/engine/source";
5
+ declare function canProxyToGsc(state: BuilderState): boolean;
6
+ interface CreateLiveGscSourceOptions {
7
+ /** GSC property URL (e.g. `sc-domain:example.com` or `https://example.com/`). */
8
+ siteUrl: string;
9
+ /**
10
+ * Returns a valid GSC access token. Called lazily on first query so refresh
11
+ * cost is paid only when the source actually runs. Host owns refresh logic.
12
+ */
13
+ getAccessToken: () => Promise<string>;
14
+ /** Optional host client factory (for custom timeouts, fetch, or telemetry). */
15
+ createClient?: (accessToken: string) => GoogleSearchConsoleClient | Promise<GoogleSearchConsoleClient>;
16
+ /**
17
+ * GSC `searchType` slice this source is scoped to (`web`, `discover`,
18
+ * `news`, `googleNews`, `image`, `video`). When set, the slice is injected
19
+ * into every outgoing builder state so the live API returns rows for that
20
+ * slice only. An explicit search type already present on the state wins.
21
+ */
22
+ searchType?: SearchType$1;
23
+ }
24
+ declare function createLiveGscSource(opts: CreateLiveGscSourceOptions): AnalysisQuerySource;
25
+ export { CreateLiveGscSourceOptions, canProxyToGsc, createLiveGscSource };
package/dist/live.mjs ADDED
@@ -0,0 +1,39 @@
1
+ import { createGscApiQuerySource } from "./source.mjs";
2
+ import { googleSearchConsole } from "gscdump";
3
+ const PRO_ONLY_DIMENSIONS = /* @__PURE__ */ new Set(["queryCanonical", "page_keywords"]);
4
+ function canProxyToGsc(state) {
5
+ if (state.dimensions.some((d) => PRO_ONLY_DIMENSIONS.has(d))) return false;
6
+ return true;
7
+ }
8
+ function withSearchType(state, searchType) {
9
+ return state.searchType ? state : {
10
+ ...state,
11
+ searchType
12
+ };
13
+ }
14
+ function createLiveGscSource(opts) {
15
+ let clientPromise = null;
16
+ function getClient() {
17
+ if (!clientPromise) clientPromise = opts.getAccessToken().then((accessToken) => opts.createClient?.(accessToken) ?? googleSearchConsole({ accessToken }));
18
+ return clientPromise;
19
+ }
20
+ return {
21
+ name: "gsc-api",
22
+ kind: "live",
23
+ capabilities: {
24
+ regex: true,
25
+ multiDataset: false,
26
+ comparisonJoin: false,
27
+ windowTotals: false
28
+ },
29
+ async queryRows(state) {
30
+ const client = await getClient();
31
+ const scopedState = opts.searchType !== void 0 ? withSearchType(state, opts.searchType) : state;
32
+ return createGscApiQuerySource({
33
+ client,
34
+ siteUrl: opts.siteUrl
35
+ }).queryRows(scopedState);
36
+ }
37
+ };
38
+ }
39
+ export { canProxyToGsc, createLiveGscSource };