gscdump 0.2.0 → 0.3.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.
@@ -191,6 +191,7 @@ type Country = typeof Countries[keyof typeof Countries];
191
191
  //#region src/query/types.d.ts
192
192
  interface DimensionValueMap {
193
193
  query: string;
194
+ queryCanonical: string;
194
195
  page: string;
195
196
  country: Country;
196
197
  device: Device;
@@ -202,11 +203,18 @@ interface QueryParamValueMap {
202
203
  searchType: SearchType;
203
204
  }
204
205
  type QueryParamName = keyof QueryParamValueMap;
206
+ declare const ColumnBrand: unique symbol;
207
+ interface Column<D extends Dimension> {
208
+ readonly [ColumnBrand]: D;
209
+ readonly dimension: D;
210
+ }
205
211
  type FilterOperator = 'equals' | 'notEquals' | 'contains' | 'notContains' | 'includingRegex' | 'excludingRegex';
206
212
  type DateOperator = 'gte' | 'gt' | 'lte' | 'lt' | 'between';
213
+ type MetricOperator = 'metricGte' | 'metricGt' | 'metricLte' | 'metricLt' | 'metricBetween';
214
+ type SpecialOperator = 'topLevel';
207
215
  interface InternalFilter {
208
- dimension: Dimension | QueryParamName;
209
- operator: FilterOperator | DateOperator;
216
+ dimension: Dimension | QueryParamName | Metric;
217
+ operator: FilterOperator | DateOperator | MetricOperator | SpecialOperator;
210
218
  expression: string;
211
219
  expression2?: string;
212
220
  }
@@ -224,17 +232,35 @@ type GSCRow<D extends Dimension[], C> = { [K in D[number]]: K extends keyof C ?
224
232
  ctr: number;
225
233
  position: number;
226
234
  };
235
+ type Metric = 'clicks' | 'impressions' | 'ctr' | 'position';
236
+ declare const MetricColumnBrand: unique symbol;
237
+ interface MetricColumn<M extends Metric> {
238
+ readonly [MetricColumnBrand]: M;
239
+ readonly metric: M;
240
+ }
227
241
  interface BuilderState {
228
242
  dimensions: Dimension[];
243
+ metrics?: Metric[];
229
244
  filter?: Filter<any>;
245
+ orderBy?: {
246
+ column: Metric | 'date';
247
+ dir: 'asc' | 'desc';
248
+ };
230
249
  rowLimit?: number;
231
250
  startRow?: number;
232
251
  }
233
252
  //#endregion
234
253
  //#region src/query/builder.d.ts
254
+ type SelectableColumn = Column<Dimension> | MetricColumn<Metric>;
255
+ type OrderableColumn = MetricColumn<Metric> | Column<'date'>;
256
+ type ExtractDimensions<T extends SelectableColumn[]> = { [K in keyof T]: T[K] extends Column<infer D> ? D : never }[number] extends infer U ? Exclude<U, never>[] : never;
235
257
  interface GSCQueryBuilder<D extends Dimension[] = [], C = object> {
236
- select: <T extends Dimension[]>(...dims: T) => GSCQueryBuilder<T, C>;
258
+ select: {
259
+ <T extends Dimension[]>(...dims: T): GSCQueryBuilder<T, C>;
260
+ <T extends SelectableColumn[]>(...cols: T): GSCQueryBuilder<ExtractDimensions<T> & Dimension[], C>;
261
+ };
237
262
  where: <F extends Filter<any>>(filter: F) => GSCQueryBuilder<D, C & F['_constraints']>;
263
+ orderBy: (col: OrderableColumn, dir: 'asc' | 'desc') => GSCQueryBuilder<D, C>;
238
264
  limit: (n: number) => GSCQueryBuilder<D, C>;
239
265
  offset: (n: number) => GSCQueryBuilder<D, C>;
240
266
  toBody: () => SearchAnalyticsQuery;
@@ -229,9 +229,9 @@ function analyzeDecay(input, options = {}) {
229
229
  });
230
230
  const previousMap = /* @__PURE__ */ new Map();
231
231
  for (const row of input.previous) {
232
- const clicks = num(row.clicks);
233
- if (clicks >= minPreviousClicks) previousMap.set(row.page, {
234
- clicks,
232
+ const clicks$1 = num(row.clicks);
233
+ if (clicks$1 >= minPreviousClicks) previousMap.set(row.page, {
234
+ clicks: clicks$1,
235
235
  position: num(row.position)
236
236
  });
237
237
  }
@@ -266,7 +266,21 @@ const DATE_OPERATORS = [
266
266
  "lt",
267
267
  "between"
268
268
  ];
269
+ const METRIC_OPERATORS = [
270
+ "metricGte",
271
+ "metricGt",
272
+ "metricLte",
273
+ "metricLt",
274
+ "metricBetween"
275
+ ];
276
+ const SPECIAL_OPERATORS = ["topLevel"];
269
277
  const QUERY_PARAMS = ["searchType"];
278
+ function isMetricOperator(op) {
279
+ return METRIC_OPERATORS.includes(op);
280
+ }
281
+ function isSpecialOperator(op) {
282
+ return SPECIAL_OPERATORS.includes(op);
283
+ }
270
284
  function isDateOperator(op) {
271
285
  return DATE_OPERATORS.includes(op);
272
286
  }
@@ -305,7 +319,8 @@ function extractSpecialFilters(filter) {
305
319
  }
306
320
  else if (isQueryParam(f.dimension)) {
307
321
  if (f.dimension === "searchType") searchType$1 = f.expression;
308
- } else otherFilters.push(f);
322
+ } else if (isMetricOperator(f.operator) || isSpecialOperator(f.operator)) otherFilters.push(f);
323
+ else otherFilters.push(f);
309
324
  if (filter._nestedGroups) for (const nested of filter._nestedGroups) {
310
325
  const extracted = extractSpecialFilters(nested);
311
326
  if (extracted.startDate) startDate = extracted.startDate;
@@ -340,19 +355,24 @@ function resolveToBody(state) {
340
355
  if (filterGroups.length > 0) body.dimensionFilterGroups = filterGroups;
341
356
  return body;
342
357
  }
358
+ function isApiFilter(f) {
359
+ return !isMetricOperator(f.operator) && !isSpecialOperator(f.operator);
360
+ }
343
361
  function resolveFilter(filter) {
344
362
  if (!filter) return [];
345
363
  const groups = [];
346
- if ((filter._groupType ?? "and") === "or") {
347
- if (filter._filters.length > 0) groups.push({
364
+ const groupType = filter._groupType ?? "and";
365
+ const apiFilters = filter._filters.filter(isApiFilter);
366
+ if (groupType === "or") {
367
+ if (apiFilters.length > 0) groups.push({
348
368
  groupType: "or",
349
- filters: filter._filters.map((f) => ({
369
+ filters: apiFilters.map((f) => ({
350
370
  dimension: f.dimension,
351
371
  operator: f.operator,
352
372
  expression: f.expression
353
373
  }))
354
374
  });
355
- } else if (filter._filters.length > 0) groups.push({ filters: filter._filters.map((f) => ({
375
+ } else if (apiFilters.length > 0) groups.push({ filters: apiFilters.map((f) => ({
356
376
  dimension: f.dimension,
357
377
  operator: f.operator,
358
378
  expression: f.expression
@@ -363,12 +383,27 @@ function resolveFilter(filter) {
363
383
 
364
384
  //#endregion
365
385
  //#region src/query/builder.ts
386
+ function isDimensionString(v) {
387
+ return typeof v === "string";
388
+ }
389
+ function isMetricColumn(v) {
390
+ return typeof v === "object" && v !== null && "metric" in v;
391
+ }
392
+ function isDimensionColumn(v) {
393
+ return typeof v === "object" && v !== null && "dimension" in v && !("metric" in v);
394
+ }
366
395
  function createBuilder(state) {
367
396
  return {
368
- select(...dims) {
397
+ select(...args) {
398
+ const dimensions = [];
399
+ const metrics = [];
400
+ for (const arg of args) if (isDimensionString(arg)) dimensions.push(arg);
401
+ else if (isDimensionColumn(arg)) dimensions.push(arg.dimension);
402
+ else if (isMetricColumn(arg)) metrics.push(arg.metric);
369
403
  return createBuilder({
370
404
  ...state,
371
- dimensions: dims
405
+ dimensions,
406
+ metrics: metrics.length > 0 ? metrics : void 0
372
407
  });
373
408
  },
374
409
  where(filter) {
@@ -377,6 +412,16 @@ function createBuilder(state) {
377
412
  filter
378
413
  });
379
414
  },
415
+ orderBy(col, dir) {
416
+ const column = isMetricColumn(col) ? col.metric : col.dimension;
417
+ return createBuilder({
418
+ ...state,
419
+ orderBy: {
420
+ column,
421
+ dir
422
+ }
423
+ });
424
+ },
380
425
  limit(n) {
381
426
  return createBuilder({
382
427
  ...state,
@@ -404,20 +449,37 @@ const gsc = createBuilder({ dimensions: [] });
404
449
  function createColumn(dimension) {
405
450
  return { dimension };
406
451
  }
452
+ function createMetricColumn(metric) {
453
+ return { metric };
454
+ }
407
455
  function createQueryParam(param) {
408
456
  return { param };
409
457
  }
410
458
  const page = createColumn("page");
411
459
  const query = createColumn("query");
460
+ const queryCanonical = createColumn("queryCanonical");
412
461
  const device = createColumn("device");
413
462
  const country = createColumn("country");
414
463
  const searchAppearance = createColumn("searchAppearance");
415
464
  const date = createColumn("date");
465
+ const clicks = createMetricColumn("clicks");
466
+ const impressions = createMetricColumn("impressions");
467
+ const ctr = createMetricColumn("ctr");
468
+ const position = createMetricColumn("position");
416
469
  const searchType = createQueryParam("searchType");
417
470
 
418
471
  //#endregion
419
472
  //#region src/query/operators.ts
420
473
  function between(column, start, end) {
474
+ if ("metric" in column) return {
475
+ _constraints: {},
476
+ _filters: [{
477
+ dimension: column.metric,
478
+ operator: "metricBetween",
479
+ expression: String(start),
480
+ expression2: String(end)
481
+ }]
482
+ };
421
483
  return {
422
484
  _constraints: {},
423
485
  _filters: [{
@@ -455,31 +517,31 @@ function analyzeMovers(input, options = {}) {
455
517
  const declining = [];
456
518
  const stable = [];
457
519
  for (const row of input.current) {
458
- const impressions = num(row.impressions);
459
- const clicks = num(row.clicks);
460
- const position = num(row.position);
461
- if (impressions < minImpressions) continue;
520
+ const impressions$1 = num(row.impressions);
521
+ const clicks$1 = num(row.clicks);
522
+ const position$1 = num(row.position);
523
+ if (impressions$1 < minImpressions) continue;
462
524
  const baseline = baselineMap.get(row.query) || {
463
525
  clicks: 0,
464
526
  impressions: 0,
465
527
  position: 0,
466
528
  page: null
467
529
  };
468
- const clicksChangePercent = percentDifference(clicks, baseline.clicks);
469
- const impressionsChangePercent = percentDifference(impressions, baseline.impressions);
530
+ const clicksChangePercent = percentDifference(clicks$1, baseline.clicks);
531
+ const impressionsChangePercent = percentDifference(impressions$1, baseline.impressions);
470
532
  const data = {
471
533
  keyword: row.query,
472
534
  page: pageMap.get(row.query) ?? null,
473
- recentClicks: clicks,
474
- recentImpressions: impressions,
475
- recentPosition: position,
535
+ recentClicks: clicks$1,
536
+ recentImpressions: impressions$1,
537
+ recentPosition: position$1,
476
538
  baselineClicks: Math.round(baseline.clicks),
477
539
  baselineImpressions: Math.round(baseline.impressions),
478
540
  baselinePosition: baseline.position,
479
- clicksChange: clicks - Math.round(baseline.clicks),
541
+ clicksChange: clicks$1 - Math.round(baseline.clicks),
480
542
  clicksChangePercent,
481
543
  impressionsChangePercent,
482
- positionChange: position - baseline.position
544
+ positionChange: position$1 - baseline.position
483
545
  };
484
546
  const absChange = Math.abs(clicksChangePercent / 100);
485
547
  if (clicksChangePercent > 0 && absChange >= changeThreshold) rising.push(data);
@@ -520,21 +582,21 @@ const EXPECTED_CTR_BY_POSITION = {
520
582
  9: .02,
521
583
  10: .015
522
584
  };
523
- function getExpectedCtr(position) {
524
- return EXPECTED_CTR_BY_POSITION[Math.round(Math.max(1, Math.min(position, 10)))] || .01;
585
+ function getExpectedCtr(position$1) {
586
+ return EXPECTED_CTR_BY_POSITION[Math.round(Math.max(1, Math.min(position$1, 10)))] || .01;
525
587
  }
526
- function calculatePositionScore(position) {
527
- if (position <= 3) return .2;
528
- if (position > 50) return .1;
529
- const distance = Math.abs(position - 11);
588
+ function calculatePositionScore(position$1) {
589
+ if (position$1 <= 3) return .2;
590
+ if (position$1 > 50) return .1;
591
+ const distance = Math.abs(position$1 - 11);
530
592
  return Math.max(0, 1 - distance / 15);
531
593
  }
532
- function calculateImpressionScore(impressions) {
533
- if (impressions <= 0) return 0;
534
- return Math.min(Math.log10(impressions) / 5, 1);
594
+ function calculateImpressionScore(impressions$1) {
595
+ if (impressions$1 <= 0) return 0;
596
+ return Math.min(Math.log10(impressions$1) / 5, 1);
535
597
  }
536
- function calculateCtrGapScore(actualCtr, position) {
537
- const expectedCtr = getExpectedCtr(position);
598
+ function calculateCtrGapScore(actualCtr, position$1) {
599
+ const expectedCtr = getExpectedCtr(position$1);
538
600
  if (actualCtr >= expectedCtr) return 0;
539
601
  const gap = expectedCtr - actualCtr;
540
602
  return Math.min(gap / expectedCtr, 1);
@@ -557,25 +619,25 @@ function analyzeOpportunity(keywords, options = {}) {
557
619
  const ctrGapWeight = weights.ctrGap ?? 1;
558
620
  const results = [];
559
621
  for (const row of keywords) {
560
- const impressions = num(row.impressions);
561
- const position = num(row.position);
562
- const ctr = num(row.ctr);
563
- const clicks = num(row.clicks);
564
- if (impressions < minImpressions) continue;
565
- const positionScore = calculatePositionScore(position);
566
- const impressionScore = calculateImpressionScore(impressions);
567
- const ctrGapScore = calculateCtrGapScore(ctr, position);
622
+ const impressions$1 = num(row.impressions);
623
+ const position$1 = num(row.position);
624
+ const ctr$1 = num(row.ctr);
625
+ const clicks$1 = num(row.clicks);
626
+ if (impressions$1 < minImpressions) continue;
627
+ const positionScore = calculatePositionScore(position$1);
628
+ const impressionScore = calculateImpressionScore(impressions$1);
629
+ const ctrGapScore = calculateCtrGapScore(ctr$1, position$1);
568
630
  const geometricMean = (positionScore ** positionWeight * impressionScore ** impressionsWeight * ctrGapScore ** ctrGapWeight) ** (1 / (positionWeight + impressionsWeight + ctrGapWeight));
569
631
  const opportunityScore = Math.round(geometricMean * 100);
570
- const targetCtr = getExpectedCtr(Math.min(3, position));
571
- const potentialClicks = Math.round(impressions * targetCtr);
632
+ const targetCtr = getExpectedCtr(Math.min(3, position$1));
633
+ const potentialClicks = Math.round(impressions$1 * targetCtr);
572
634
  results.push({
573
635
  keyword: row.query,
574
636
  page: row.page ?? null,
575
- clicks,
576
- impressions,
577
- ctr,
578
- position,
637
+ clicks: clicks$1,
638
+ impressions: impressions$1,
639
+ ctr: ctr$1,
640
+ position: position$1,
579
641
  opportunityScore,
580
642
  potentialClicks,
581
643
  factors: {
@@ -657,21 +719,21 @@ function analyzeStrikingDistance(keywords, options = {}) {
657
719
  const { minPosition = 4, maxPosition = 20, minImpressions = 100, maxCtr = .05, sortBy = "potentialClicks", sortOrder = "desc" } = options;
658
720
  const results = [];
659
721
  for (const row of keywords) {
660
- const position = num(row.position);
661
- const impressions = num(row.impressions);
662
- const ctr = num(row.ctr);
663
- const clicks = num(row.clicks);
664
- if (position < minPosition || position > maxPosition) continue;
665
- if (impressions < minImpressions) continue;
666
- if (ctr > maxCtr) continue;
667
- const potentialClicks = Math.round(impressions * .15);
722
+ const position$1 = num(row.position);
723
+ const impressions$1 = num(row.impressions);
724
+ const ctr$1 = num(row.ctr);
725
+ const clicks$1 = num(row.clicks);
726
+ if (position$1 < minPosition || position$1 > maxPosition) continue;
727
+ if (impressions$1 < minImpressions) continue;
728
+ if (ctr$1 > maxCtr) continue;
729
+ const potentialClicks = Math.round(impressions$1 * .15);
668
730
  results.push({
669
731
  keyword: row.query,
670
732
  page: row.page ?? null,
671
- clicks,
672
- impressions,
673
- ctr,
674
- position,
733
+ clicks: clicks$1,
734
+ impressions: impressions$1,
735
+ ctr: ctr$1,
736
+ position: position$1,
675
737
  potentialClicks
676
738
  });
677
739
  }
package/dist/index.d.mts CHANGED
@@ -98,6 +98,7 @@ type Country = typeof Countries[keyof typeof Countries];
98
98
  //#region src/query/types.d.ts
99
99
  interface DimensionValueMap {
100
100
  query: string;
101
+ queryCanonical: string;
101
102
  page: string;
102
103
  country: Country;
103
104
  device: Device;
@@ -109,11 +110,18 @@ interface QueryParamValueMap {
109
110
  searchType: SearchType;
110
111
  }
111
112
  type QueryParamName = keyof QueryParamValueMap;
113
+ declare const ColumnBrand: unique symbol;
114
+ interface Column<D extends Dimension> {
115
+ readonly [ColumnBrand]: D;
116
+ readonly dimension: D;
117
+ }
112
118
  type FilterOperator = 'equals' | 'notEquals' | 'contains' | 'notContains' | 'includingRegex' | 'excludingRegex';
113
119
  type DateOperator = 'gte' | 'gt' | 'lte' | 'lt' | 'between';
120
+ type MetricOperator = 'metricGte' | 'metricGt' | 'metricLte' | 'metricLt' | 'metricBetween';
121
+ type SpecialOperator = 'topLevel';
114
122
  interface InternalFilter {
115
- dimension: Dimension | QueryParamName;
116
- operator: FilterOperator | DateOperator;
123
+ dimension: Dimension | QueryParamName | Metric;
124
+ operator: FilterOperator | DateOperator | MetricOperator | SpecialOperator;
117
125
  expression: string;
118
126
  expression2?: string;
119
127
  }
@@ -131,17 +139,35 @@ type GSCRow<D extends Dimension[], C> = { [K in D[number]]: K extends keyof C ?
131
139
  ctr: number;
132
140
  position: number;
133
141
  };
142
+ type Metric = 'clicks' | 'impressions' | 'ctr' | 'position';
143
+ declare const MetricColumnBrand: unique symbol;
144
+ interface MetricColumn<M extends Metric> {
145
+ readonly [MetricColumnBrand]: M;
146
+ readonly metric: M;
147
+ }
134
148
  interface BuilderState {
135
149
  dimensions: Dimension[];
150
+ metrics?: Metric[];
136
151
  filter?: Filter<any>;
152
+ orderBy?: {
153
+ column: Metric | 'date';
154
+ dir: 'asc' | 'desc';
155
+ };
137
156
  rowLimit?: number;
138
157
  startRow?: number;
139
158
  }
140
159
  //#endregion
141
160
  //#region src/query/builder.d.ts
161
+ type SelectableColumn = Column<Dimension> | MetricColumn<Metric>;
162
+ type OrderableColumn = MetricColumn<Metric> | Column<'date'>;
163
+ type ExtractDimensions<T extends SelectableColumn[]> = { [K in keyof T]: T[K] extends Column<infer D> ? D : never }[number] extends infer U ? Exclude<U, never>[] : never;
142
164
  interface GSCQueryBuilder<D extends Dimension[] = [], C = object> {
143
- select: <T extends Dimension[]>(...dims: T) => GSCQueryBuilder<T, C>;
165
+ select: {
166
+ <T extends Dimension[]>(...dims: T): GSCQueryBuilder<T, C>;
167
+ <T extends SelectableColumn[]>(...cols: T): GSCQueryBuilder<ExtractDimensions<T> & Dimension[], C>;
168
+ };
144
169
  where: <F extends Filter<any>>(filter: F) => GSCQueryBuilder<D, C & F['_constraints']>;
170
+ orderBy: (col: OrderableColumn, dir: 'asc' | 'desc') => GSCQueryBuilder<D, C>;
145
171
  limit: (n: number) => GSCQueryBuilder<D, C>;
146
172
  offset: (n: number) => GSCQueryBuilder<D, C>;
147
173
  toBody: () => SearchAnalyticsQuery;
@@ -289,6 +315,29 @@ declare function submitSitemap(client: GoogleSearchConsoleClient, siteUrl: strin
289
315
  */
290
316
  declare function deleteSitemap(client: GoogleSearchConsoleClient, siteUrl: string, feedpath: string): Promise<void>;
291
317
  //#endregion
318
+ //#region src/core/api-client.d.ts
319
+ interface GscdumpApiOptions {
320
+ /** API key (gsd_user_xxx or gsd_prod_xxx) */
321
+ apiKey: string;
322
+ /** Base URL (defaults to https://gscdump.com) */
323
+ baseUrl?: string;
324
+ }
325
+ /**
326
+ * Create a client that queries GSC data through gscdump.com API
327
+ * instead of directly to Google. Useful when you don't have OAuth credentials.
328
+ *
329
+ * @example
330
+ * ```ts
331
+ * const client = gscdumpApi({ apiKey: 'gsd_user_xxx' })
332
+ *
333
+ * // Same query builder usage as direct client
334
+ * for await (const rows of client.query(siteId, gsc.select('page', 'query').where(...))) {
335
+ * console.log(rows)
336
+ * }
337
+ * ```
338
+ */
339
+ declare function gscdumpApi(options: GscdumpApiOptions): GoogleSearchConsoleClient;
340
+ //#endregion
292
341
  //#region src/core/errors.d.ts
293
342
  /**
294
343
  * GSC API error detection and formatting utilities.
@@ -345,4 +394,4 @@ declare function analyzeError(error: unknown): ErrorInfo;
345
394
  */
346
395
  declare function formatErrorForCli(error: unknown): string;
347
396
  //#endregion
348
- export { ApiSite, ApiSitemap, ApiSitemapContent, Auth, AuthClient, AuthOptions, DataRow, DimensionFilter, DimensionFilterGroup, ErrorInfo, GSC_QUOTAS, GoogleSearchConsoleClient, GoogleSearchConsoleClientOptions, IndexStatusResult, IndexingMetadata, IndexingNotificationType, IndexingResult, InspectUrlIndexResponse, InspectUrlResult, MobileUsabilityResult, Period, PublishUrlNotificationResponse, RequiredNonNullable, ResolvedAnalyticsRange, RichResultsResult, SearchAnalyticsQuery, SearchAnalyticsResponse, Site, SiteAnalytics, UrlInspectionResult, UrlNotificationMetadata, analyzeError, batchInspectUrls, batchRequestIndexing, createAuth, createFetch, deleteSitemap, fetchSitemap, fetchSitemaps, fetchSites, fetchSitesWithSitemaps, formatErrorForCli, getErrorCode, getErrorMessage, getIndexingMetadata, getRetryAfter, googleSearchConsole, inspectUrl, isAuthError, isQuotaError, isRateLimitError, requestIndexing, submitSitemap };
397
+ export { ApiSite, ApiSitemap, ApiSitemapContent, Auth, AuthClient, AuthOptions, DataRow, DimensionFilter, DimensionFilterGroup, ErrorInfo, GSC_QUOTAS, GoogleSearchConsoleClient, GoogleSearchConsoleClientOptions, GscdumpApiOptions, IndexStatusResult, IndexingMetadata, IndexingNotificationType, IndexingResult, InspectUrlIndexResponse, InspectUrlResult, MobileUsabilityResult, Period, PublishUrlNotificationResponse, RequiredNonNullable, ResolvedAnalyticsRange, RichResultsResult, SearchAnalyticsQuery, SearchAnalyticsResponse, Site, SiteAnalytics, UrlInspectionResult, UrlNotificationMetadata, analyzeError, batchInspectUrls, batchRequestIndexing, createAuth, createFetch, deleteSitemap, fetchSitemap, fetchSitemaps, fetchSites, fetchSitesWithSitemaps, formatErrorForCli, getErrorCode, getErrorMessage, getIndexingMetadata, getRetryAfter, googleSearchConsole, gscdumpApi, inspectUrl, isAuthError, isQuotaError, isRateLimitError, requestIndexing, submitSitemap };
package/dist/index.mjs CHANGED
@@ -130,7 +130,21 @@ const DATE_OPERATORS = [
130
130
  "lt",
131
131
  "between"
132
132
  ];
133
+ const METRIC_OPERATORS = [
134
+ "metricGte",
135
+ "metricGt",
136
+ "metricLte",
137
+ "metricLt",
138
+ "metricBetween"
139
+ ];
140
+ const SPECIAL_OPERATORS = ["topLevel"];
133
141
  const QUERY_PARAMS = ["searchType"];
142
+ function isMetricOperator(op) {
143
+ return METRIC_OPERATORS.includes(op);
144
+ }
145
+ function isSpecialOperator(op) {
146
+ return SPECIAL_OPERATORS.includes(op);
147
+ }
134
148
  function isDateOperator(op) {
135
149
  return DATE_OPERATORS.includes(op);
136
150
  }
@@ -169,7 +183,8 @@ function extractSpecialFilters(filter) {
169
183
  }
170
184
  else if (isQueryParam(f.dimension)) {
171
185
  if (f.dimension === "searchType") searchType = f.expression;
172
- } else otherFilters.push(f);
186
+ } else if (isMetricOperator(f.operator) || isSpecialOperator(f.operator)) otherFilters.push(f);
187
+ else otherFilters.push(f);
173
188
  if (filter._nestedGroups) for (const nested of filter._nestedGroups) {
174
189
  const extracted = extractSpecialFilters(nested);
175
190
  if (extracted.startDate) startDate = extracted.startDate;
@@ -204,19 +219,24 @@ function resolveToBody(state) {
204
219
  if (filterGroups.length > 0) body.dimensionFilterGroups = filterGroups;
205
220
  return body;
206
221
  }
222
+ function isApiFilter(f) {
223
+ return !isMetricOperator(f.operator) && !isSpecialOperator(f.operator);
224
+ }
207
225
  function resolveFilter(filter) {
208
226
  if (!filter) return [];
209
227
  const groups = [];
210
- if ((filter._groupType ?? "and") === "or") {
211
- if (filter._filters.length > 0) groups.push({
228
+ const groupType = filter._groupType ?? "and";
229
+ const apiFilters = filter._filters.filter(isApiFilter);
230
+ if (groupType === "or") {
231
+ if (apiFilters.length > 0) groups.push({
212
232
  groupType: "or",
213
- filters: filter._filters.map((f) => ({
233
+ filters: apiFilters.map((f) => ({
214
234
  dimension: f.dimension,
215
235
  operator: f.operator,
216
236
  expression: f.expression
217
237
  }))
218
238
  });
219
- } else if (filter._filters.length > 0) groups.push({ filters: filter._filters.map((f) => ({
239
+ } else if (apiFilters.length > 0) groups.push({ filters: apiFilters.map((f) => ({
220
240
  dimension: f.dimension,
221
241
  operator: f.operator,
222
242
  expression: f.expression
@@ -225,6 +245,123 @@ function resolveFilter(filter) {
225
245
  return groups;
226
246
  }
227
247
 
248
+ //#endregion
249
+ //#region src/core/api-client.ts
250
+ /**
251
+ * Create a client that queries GSC data through gscdump.com API
252
+ * instead of directly to Google. Useful when you don't have OAuth credentials.
253
+ *
254
+ * @example
255
+ * ```ts
256
+ * const client = gscdumpApi({ apiKey: 'gsd_user_xxx' })
257
+ *
258
+ * // Same query builder usage as direct client
259
+ * for await (const rows of client.query(siteId, gsc.select('page', 'query').where(...))) {
260
+ * console.log(rows)
261
+ * }
262
+ * ```
263
+ */
264
+ function gscdumpApi(options) {
265
+ const baseUrl = options.baseUrl?.replace(/\/$/, "") || "https://gscdump.com";
266
+ const fetch = ofetch.create({
267
+ baseURL: baseUrl,
268
+ retry: 3,
269
+ retryDelay: 1e3,
270
+ retryStatusCodes: [
271
+ 408,
272
+ 429,
273
+ 500,
274
+ 502,
275
+ 503,
276
+ 504
277
+ ],
278
+ headers: {
279
+ "x-api-key": options.apiKey,
280
+ "Content-Type": "application/json"
281
+ }
282
+ });
283
+ const rawQuery = async (siteId, body) => {
284
+ const response = await fetch(`/api/sites/${encodeURIComponent(siteId)}/query`, {
285
+ method: "POST",
286
+ body
287
+ });
288
+ return { rows: response.rows.map((row) => {
289
+ return {
290
+ keys: response.meta.dimensions.map((dim) => String(row[dim] ?? "")),
291
+ clicks: row.clicks,
292
+ impressions: row.impressions,
293
+ ctr: row.ctr,
294
+ position: row.position
295
+ };
296
+ }) };
297
+ };
298
+ return {
299
+ async *query(siteId, builder) {
300
+ const state = builder.getState();
301
+ const body = resolveToBody(state);
302
+ const rowLimit = body.rowLimit || 25e3;
303
+ let startRow = body.startRow || 0;
304
+ while (true) {
305
+ const response = await fetch(`/api/sites/${encodeURIComponent(siteId)}/query`, {
306
+ method: "POST",
307
+ body: {
308
+ ...body,
309
+ startRow,
310
+ rowLimit
311
+ }
312
+ });
313
+ const rows = response.rows.map((row) => {
314
+ const result = {
315
+ clicks: row.clicks ?? 0,
316
+ impressions: row.impressions ?? 0,
317
+ ctr: row.ctr ?? 0,
318
+ position: row.position ?? 0
319
+ };
320
+ state.dimensions.forEach((dim) => {
321
+ result[dim] = row[dim];
322
+ });
323
+ return result;
324
+ });
325
+ yield rows;
326
+ if (!response.meta.hasMore || rows.length < rowLimit) break;
327
+ startRow += rows.length;
328
+ }
329
+ },
330
+ sites: async () => {
331
+ return (await fetch("/api/sites")).sites.map((s) => ({
332
+ siteUrl: s.gscSiteUrl,
333
+ permissionLevel: s.permissionLevel || "siteOwner"
334
+ }));
335
+ },
336
+ inspect: () => {
337
+ throw new Error("URL inspection not available via gscdump API. Use googleSearchConsole() with OAuth credentials.");
338
+ },
339
+ sitemaps: {
340
+ list: async (siteId) => {
341
+ return (await fetch(`/api/sites/${encodeURIComponent(siteId)}/sitemaps`)).sitemaps || [];
342
+ },
343
+ get: () => {
344
+ throw new Error("Sitemap get not available via gscdump API.");
345
+ },
346
+ submit: () => {
347
+ throw new Error("Sitemap submit not available via gscdump API.");
348
+ },
349
+ delete: () => {
350
+ throw new Error("Sitemap delete not available via gscdump API.");
351
+ }
352
+ },
353
+ indexing: {
354
+ publish: () => {
355
+ throw new Error("Indexing API not available via gscdump API. Use googleSearchConsole() with OAuth credentials.");
356
+ },
357
+ getMetadata: () => {
358
+ throw new Error("Indexing API not available via gscdump API. Use googleSearchConsole() with OAuth credentials.");
359
+ }
360
+ },
361
+ _rawQuery: rawQuery
362
+ };
363
+ }
364
+
228
365
  //#endregion
229
366
  //#region src/core/client.ts
230
367
  const GSC_API = "https://searchconsole.googleapis.com";
@@ -527,4 +664,4 @@ function formatErrorForCli(error) {
527
664
  }
528
665
 
529
666
  //#endregion
530
- export { GSC_QUOTAS, analyzeError, batchInspectUrls, batchRequestIndexing, createAuth, createFetch, deleteSitemap, fetchSitemap, fetchSitemaps, fetchSites, fetchSitesWithSitemaps, formatErrorForCli, getErrorCode, getErrorMessage, getIndexingMetadata, getRetryAfter, googleSearchConsole, inspectUrl, isAuthError, isQuotaError, isRateLimitError, requestIndexing, submitSitemap };
667
+ export { GSC_QUOTAS, analyzeError, batchInspectUrls, batchRequestIndexing, createAuth, createFetch, deleteSitemap, fetchSitemap, fetchSitemaps, fetchSites, fetchSitesWithSitemaps, formatErrorForCli, getErrorCode, getErrorMessage, getIndexingMetadata, getRetryAfter, googleSearchConsole, gscdumpApi, inspectUrl, isAuthError, isQuotaError, isRateLimitError, requestIndexing, submitSitemap };
@@ -33,6 +33,7 @@ type Country = typeof Countries[keyof typeof Countries];
33
33
  //#region src/query/types.d.ts
34
34
  interface DimensionValueMap {
35
35
  query: string;
36
+ queryCanonical: string;
36
37
  page: string;
37
38
  country: Country;
38
39
  device: Device;
@@ -56,9 +57,11 @@ interface QueryParam<P extends QueryParamName> {
56
57
  }
57
58
  type FilterOperator = 'equals' | 'notEquals' | 'contains' | 'notContains' | 'includingRegex' | 'excludingRegex';
58
59
  type DateOperator = 'gte' | 'gt' | 'lte' | 'lt' | 'between';
60
+ type MetricOperator = 'metricGte' | 'metricGt' | 'metricLte' | 'metricLt' | 'metricBetween';
61
+ type SpecialOperator = 'topLevel';
59
62
  interface InternalFilter {
60
- dimension: Dimension | QueryParamName;
61
- operator: FilterOperator | DateOperator;
63
+ dimension: Dimension | QueryParamName | Metric;
64
+ operator: FilterOperator | DateOperator | MetricOperator | SpecialOperator;
62
65
  expression: string;
63
66
  expression2?: string;
64
67
  }
@@ -71,7 +74,7 @@ interface Filter<C = object> {
71
74
  readonly _groupType?: 'and' | 'or';
72
75
  }
73
76
  type MergeConstraints<F extends Filter<any>[]> = UnionToIntersection<F[number]['_constraints']>;
74
- type UnionToIntersection<U> = (U extends any ? (k: U) => void : never) extends ((k: infer I) => void) ? I : never;
77
+ type UnionToIntersection<U$1> = (U$1 extends any ? (k: U$1) => void : never) extends ((k: infer I) => void) ? I : never;
75
78
  interface GSCResult<D extends Dimension[], C> {
76
79
  rows: Array<GSCRow<D, C>>;
77
80
  }
@@ -81,17 +84,47 @@ type GSCRow<D extends Dimension[], C> = { [K in D[number]]: K extends keyof C ?
81
84
  ctr: number;
82
85
  position: number;
83
86
  };
87
+ type Metric = 'clicks' | 'impressions' | 'ctr' | 'position';
88
+ declare const MetricColumnBrand: unique symbol;
89
+ interface MetricColumn<M extends Metric> {
90
+ readonly [MetricColumnBrand]: M;
91
+ readonly metric: M;
92
+ }
84
93
  interface BuilderState {
85
94
  dimensions: Dimension[];
95
+ metrics?: Metric[];
86
96
  filter?: Filter<any>;
97
+ orderBy?: {
98
+ column: Metric | 'date';
99
+ dir: 'asc' | 'desc';
100
+ };
87
101
  rowLimit?: number;
88
102
  startRow?: number;
89
103
  }
104
+ interface JsonInternalFilter {
105
+ dimension: string;
106
+ operator: string;
107
+ expression: string;
108
+ expression2?: string;
109
+ }
110
+ interface JsonFilter {
111
+ _filters: JsonInternalFilter[];
112
+ _nestedGroups?: JsonFilter[];
113
+ _groupType?: 'and' | 'or';
114
+ }
115
+ type FilterInput = Filter<any> | JsonFilter;
90
116
  //#endregion
91
117
  //#region src/query/builder.d.ts
118
+ type SelectableColumn = Column<Dimension> | MetricColumn<Metric>;
119
+ type OrderableColumn = MetricColumn<Metric> | Column<'date'>;
120
+ type ExtractDimensions<T extends SelectableColumn[]> = { [K in keyof T]: T[K] extends Column<infer D> ? D : never }[number] extends infer U ? Exclude<U, never>[] : never;
92
121
  interface GSCQueryBuilder<D extends Dimension[] = [], C = object> {
93
- select: <T extends Dimension[]>(...dims: T) => GSCQueryBuilder<T, C>;
122
+ select: {
123
+ <T extends Dimension[]>(...dims: T): GSCQueryBuilder<T, C>;
124
+ <T extends SelectableColumn[]>(...cols: T): GSCQueryBuilder<ExtractDimensions<T> & Dimension[], C>;
125
+ };
94
126
  where: <F extends Filter<any>>(filter: F) => GSCQueryBuilder<D, C & F['_constraints']>;
127
+ orderBy: (col: OrderableColumn, dir: 'asc' | 'desc') => GSCQueryBuilder<D, C>;
95
128
  limit: (n: number) => GSCQueryBuilder<D, C>;
96
129
  offset: (n: number) => GSCQueryBuilder<D, C>;
97
130
  toBody: () => SearchAnalyticsQuery;
@@ -102,10 +135,15 @@ declare const gsc: GSCQueryBuilder<[], object>;
102
135
  //#region src/query/columns.d.ts
103
136
  declare const page: Column<"page">;
104
137
  declare const query: Column<"query">;
138
+ declare const queryCanonical: Column<"queryCanonical">;
105
139
  declare const device: Column<"device">;
106
140
  declare const country: Column<"country">;
107
141
  declare const searchAppearance: Column<"searchAppearance">;
108
142
  declare const date: Column<"date">;
143
+ declare const clicks: MetricColumn<"clicks">;
144
+ declare const impressions: MetricColumn<"impressions">;
145
+ declare const ctr: MetricColumn<"ctr">;
146
+ declare const position: MetricColumn<"position">;
109
147
  declare const searchType: QueryParam<"searchType">;
110
148
  //#endregion
111
149
  //#region src/query/operators.d.ts
@@ -120,17 +158,27 @@ declare function notRegex<D extends Dimension>(column: Column<D>, pattern: RegEx
120
158
  declare function and<F extends Filter<any>[]>(...filters: F): Filter<MergeConstraints<F>>;
121
159
  declare function or<F extends Filter<any>[]>(...filters: F): Filter<object>;
122
160
  declare function not<F extends Filter<any>>(filter: F): Filter<object>;
161
+ declare function gte<M extends Metric>(column: MetricColumn<M>, value: number): Filter<object>;
123
162
  declare function gte<D extends Dimension>(column: Column<D>, value: DimensionValueMap[D]): Filter<object>;
163
+ declare function gt<M extends Metric>(column: MetricColumn<M>, value: number): Filter<object>;
124
164
  declare function gt<D extends Dimension>(column: Column<D>, value: DimensionValueMap[D]): Filter<object>;
165
+ declare function lte<M extends Metric>(column: MetricColumn<M>, value: number): Filter<object>;
125
166
  declare function lte<D extends Dimension>(column: Column<D>, value: DimensionValueMap[D]): Filter<object>;
167
+ declare function lt<M extends Metric>(column: MetricColumn<M>, value: number): Filter<object>;
126
168
  declare function lt<D extends Dimension>(column: Column<D>, value: DimensionValueMap[D]): Filter<object>;
169
+ declare function between<M extends Metric>(column: MetricColumn<M>, start: number, end: number): Filter<object>;
127
170
  declare function between<D extends Dimension>(column: Column<D>, start: DimensionValueMap[D], end: DimensionValueMap[D]): Filter<object>;
171
+ declare function topLevel(column: Column<'page'>): Filter<object>;
128
172
  //#endregion
129
173
  //#region src/query/resolver.d.ts
130
- declare function extractDateRange(filter?: Filter<any>): {
174
+ declare function isJsonFilter(value: unknown): value is JsonFilter;
175
+ declare function parseJsonFilter(json: JsonFilter): Filter<any>;
176
+ declare function extractDateRange(input?: FilterInput): {
131
177
  startDate?: string;
132
178
  endDate?: string;
133
179
  };
180
+ declare function extractMetricFilters(input?: FilterInput): InternalFilter[];
181
+ declare function extractSpecialOperatorFilters(input?: FilterInput): InternalFilter[];
134
182
  //#endregion
135
183
  //#region src/query/utils/dayjs.d.ts
136
184
  declare function dayjs(date?: _dayjs.ConfigType): Dayjs;
@@ -141,4 +189,4 @@ declare function dayjsPst(): Dayjs;
141
189
  declare function today(): string;
142
190
  declare function daysAgo(n: number): string;
143
191
  //#endregion
144
- export { type BuilderState, type Column, Countries, type Country, type Device, Devices, type Dimension, type DimensionValueMap, type Filter, type GSCQueryBuilder, type GSCResult, type GSCRow, type QueryParam, type QueryParamName, type QueryParamValueMap, type SearchType, SearchTypes, and, between, contains, country, currentPstDate, date, dayjs, dayjsPst, daysAgo, device, eq, extractDateRange, gsc, gt, gte, inArray, like, lt, lte, ne, not, notRegex, or, page, query, regex, searchAppearance, searchType, today };
192
+ export { type BuilderState, type Column, Countries, type Country, type Device, Devices, type Dimension, type DimensionValueMap, type Filter, type FilterInput, type GSCQueryBuilder, type GSCResult, type GSCRow, type InternalFilter, type JsonFilter, type JsonInternalFilter, type Metric, type MetricColumn, type QueryParam, type QueryParamName, type QueryParamValueMap, type SearchType, SearchTypes, and, between, clicks, contains, country, ctr, currentPstDate, date, dayjs, dayjsPst, daysAgo, device, eq, extractDateRange, extractMetricFilters, extractSpecialOperatorFilters, gsc, gt, gte, impressions, inArray, isJsonFilter, like, lt, lte, ne, not, notRegex, or, page, parseJsonFilter, position, query, queryCanonical, regex, searchAppearance, searchType, today, topLevel };
@@ -24,7 +24,41 @@ const DATE_OPERATORS = [
24
24
  "lt",
25
25
  "between"
26
26
  ];
27
+ const METRIC_OPERATORS = [
28
+ "metricGte",
29
+ "metricGt",
30
+ "metricLte",
31
+ "metricLt",
32
+ "metricBetween"
33
+ ];
34
+ const SPECIAL_OPERATORS = ["topLevel"];
27
35
  const QUERY_PARAMS = ["searchType"];
36
+ function isJsonFilter(value) {
37
+ return typeof value === "object" && value !== null && "_filters" in value && Array.isArray(value._filters);
38
+ }
39
+ function parseJsonFilter(json) {
40
+ return {
41
+ _constraints: {},
42
+ _filters: json._filters.map((f) => ({
43
+ dimension: f.dimension,
44
+ operator: f.operator,
45
+ expression: f.expression,
46
+ expression2: f.expression2
47
+ })),
48
+ _nestedGroups: json._nestedGroups?.map(parseJsonFilter),
49
+ _groupType: json._groupType
50
+ };
51
+ }
52
+ function normalizeFilter(input) {
53
+ if (!input) return void 0;
54
+ return input;
55
+ }
56
+ function isMetricOperator(op) {
57
+ return METRIC_OPERATORS.includes(op);
58
+ }
59
+ function isSpecialOperator(op) {
60
+ return SPECIAL_OPERATORS.includes(op);
61
+ }
28
62
  function isDateOperator(op) {
29
63
  return DATE_OPERATORS.includes(op);
30
64
  }
@@ -63,7 +97,8 @@ function extractSpecialFilters(filter) {
63
97
  }
64
98
  else if (isQueryParam(f.dimension)) {
65
99
  if (f.dimension === "searchType") searchType$1 = f.expression;
66
- } else otherFilters.push(f);
100
+ } else if (isMetricOperator(f.operator) || isSpecialOperator(f.operator)) otherFilters.push(f);
101
+ else otherFilters.push(f);
67
102
  if (filter._nestedGroups) for (const nested of filter._nestedGroups) {
68
103
  const extracted = extractSpecialFilters(nested);
69
104
  if (extracted.startDate) startDate = extracted.startDate;
@@ -83,13 +118,27 @@ function extractSpecialFilters(filter) {
83
118
  dimensionFilter
84
119
  };
85
120
  }
86
- function extractDateRange(filter) {
87
- const { startDate, endDate } = extractSpecialFilters(filter);
121
+ function extractDateRange(input) {
122
+ const { startDate, endDate } = extractSpecialFilters(normalizeFilter(input));
88
123
  return {
89
124
  startDate,
90
125
  endDate
91
126
  };
92
127
  }
128
+ function extractMetricFilters(input) {
129
+ const filter = normalizeFilter(input);
130
+ if (!filter) return [];
131
+ const metricFilters = filter._filters.filter((f) => isMetricOperator(f.operator));
132
+ const nested = filter._nestedGroups?.flatMap((g) => extractMetricFilters(g)) ?? [];
133
+ return [...metricFilters, ...nested];
134
+ }
135
+ function extractSpecialOperatorFilters(input) {
136
+ const filter = normalizeFilter(input);
137
+ if (!filter) return [];
138
+ const special = filter._filters.filter((f) => isSpecialOperator(f.operator));
139
+ const nested = filter._nestedGroups?.flatMap((g) => extractSpecialOperatorFilters(g)) ?? [];
140
+ return [...special, ...nested];
141
+ }
93
142
  function resolveToBody(state) {
94
143
  const { startDate, endDate, searchType: searchType$1, dimensionFilter } = extractSpecialFilters(state.filter);
95
144
  if (!startDate || !endDate) throw new Error("Date range required: use .where(between(date, start, end)) or .where(and(gte(date, start), lte(date, end)))");
@@ -105,19 +154,24 @@ function resolveToBody(state) {
105
154
  if (filterGroups.length > 0) body.dimensionFilterGroups = filterGroups;
106
155
  return body;
107
156
  }
157
+ function isApiFilter(f) {
158
+ return !isMetricOperator(f.operator) && !isSpecialOperator(f.operator);
159
+ }
108
160
  function resolveFilter(filter) {
109
161
  if (!filter) return [];
110
162
  const groups = [];
111
- if ((filter._groupType ?? "and") === "or") {
112
- if (filter._filters.length > 0) groups.push({
163
+ const groupType = filter._groupType ?? "and";
164
+ const apiFilters = filter._filters.filter(isApiFilter);
165
+ if (groupType === "or") {
166
+ if (apiFilters.length > 0) groups.push({
113
167
  groupType: "or",
114
- filters: filter._filters.map((f) => ({
168
+ filters: apiFilters.map((f) => ({
115
169
  dimension: f.dimension,
116
170
  operator: f.operator,
117
171
  expression: f.expression
118
172
  }))
119
173
  });
120
- } else if (filter._filters.length > 0) groups.push({ filters: filter._filters.map((f) => ({
174
+ } else if (apiFilters.length > 0) groups.push({ filters: apiFilters.map((f) => ({
121
175
  dimension: f.dimension,
122
176
  operator: f.operator,
123
177
  expression: f.expression
@@ -128,12 +182,27 @@ function resolveFilter(filter) {
128
182
 
129
183
  //#endregion
130
184
  //#region src/query/builder.ts
185
+ function isDimensionString(v) {
186
+ return typeof v === "string";
187
+ }
188
+ function isMetricColumn(v) {
189
+ return typeof v === "object" && v !== null && "metric" in v;
190
+ }
191
+ function isDimensionColumn(v) {
192
+ return typeof v === "object" && v !== null && "dimension" in v && !("metric" in v);
193
+ }
131
194
  function createBuilder(state) {
132
195
  return {
133
- select(...dims) {
196
+ select(...args) {
197
+ const dimensions = [];
198
+ const metrics = [];
199
+ for (const arg of args) if (isDimensionString(arg)) dimensions.push(arg);
200
+ else if (isDimensionColumn(arg)) dimensions.push(arg.dimension);
201
+ else if (isMetricColumn(arg)) metrics.push(arg.metric);
134
202
  return createBuilder({
135
203
  ...state,
136
- dimensions: dims
204
+ dimensions,
205
+ metrics: metrics.length > 0 ? metrics : void 0
137
206
  });
138
207
  },
139
208
  where(filter) {
@@ -142,6 +211,16 @@ function createBuilder(state) {
142
211
  filter
143
212
  });
144
213
  },
214
+ orderBy(col, dir) {
215
+ const column = isMetricColumn(col) ? col.metric : col.dimension;
216
+ return createBuilder({
217
+ ...state,
218
+ orderBy: {
219
+ column,
220
+ dir
221
+ }
222
+ });
223
+ },
145
224
  limit(n) {
146
225
  return createBuilder({
147
226
  ...state,
@@ -169,15 +248,23 @@ const gsc = createBuilder({ dimensions: [] });
169
248
  function createColumn(dimension) {
170
249
  return { dimension };
171
250
  }
251
+ function createMetricColumn(metric) {
252
+ return { metric };
253
+ }
172
254
  function createQueryParam(param) {
173
255
  return { param };
174
256
  }
175
257
  const page = createColumn("page");
176
258
  const query = createColumn("query");
259
+ const queryCanonical = createColumn("queryCanonical");
177
260
  const device = createColumn("device");
178
261
  const country = createColumn("country");
179
262
  const searchAppearance = createColumn("searchAppearance");
180
263
  const date = createColumn("date");
264
+ const clicks = createMetricColumn("clicks");
265
+ const impressions = createMetricColumn("impressions");
266
+ const ctr = createMetricColumn("ctr");
267
+ const position = createMetricColumn("position");
181
268
  const searchType = createQueryParam("searchType");
182
269
 
183
270
  //#endregion
@@ -1816,6 +1903,14 @@ function invertOperator(op) {
1816
1903
  }[op];
1817
1904
  }
1818
1905
  function gte(column, value) {
1906
+ if ("metric" in column) return {
1907
+ _constraints: {},
1908
+ _filters: [{
1909
+ dimension: column.metric,
1910
+ operator: "metricGte",
1911
+ expression: String(value)
1912
+ }]
1913
+ };
1819
1914
  return {
1820
1915
  _constraints: {},
1821
1916
  _filters: [{
@@ -1826,6 +1921,14 @@ function gte(column, value) {
1826
1921
  };
1827
1922
  }
1828
1923
  function gt(column, value) {
1924
+ if ("metric" in column) return {
1925
+ _constraints: {},
1926
+ _filters: [{
1927
+ dimension: column.metric,
1928
+ operator: "metricGt",
1929
+ expression: String(value)
1930
+ }]
1931
+ };
1829
1932
  return {
1830
1933
  _constraints: {},
1831
1934
  _filters: [{
@@ -1836,6 +1939,14 @@ function gt(column, value) {
1836
1939
  };
1837
1940
  }
1838
1941
  function lte(column, value) {
1942
+ if ("metric" in column) return {
1943
+ _constraints: {},
1944
+ _filters: [{
1945
+ dimension: column.metric,
1946
+ operator: "metricLte",
1947
+ expression: String(value)
1948
+ }]
1949
+ };
1839
1950
  return {
1840
1951
  _constraints: {},
1841
1952
  _filters: [{
@@ -1846,6 +1957,14 @@ function lte(column, value) {
1846
1957
  };
1847
1958
  }
1848
1959
  function lt(column, value) {
1960
+ if ("metric" in column) return {
1961
+ _constraints: {},
1962
+ _filters: [{
1963
+ dimension: column.metric,
1964
+ operator: "metricLt",
1965
+ expression: String(value)
1966
+ }]
1967
+ };
1849
1968
  return {
1850
1969
  _constraints: {},
1851
1970
  _filters: [{
@@ -1856,6 +1975,15 @@ function lt(column, value) {
1856
1975
  };
1857
1976
  }
1858
1977
  function between(column, start, end) {
1978
+ if ("metric" in column) return {
1979
+ _constraints: {},
1980
+ _filters: [{
1981
+ dimension: column.metric,
1982
+ operator: "metricBetween",
1983
+ expression: String(start),
1984
+ expression2: String(end)
1985
+ }]
1986
+ };
1859
1987
  return {
1860
1988
  _constraints: {},
1861
1989
  _filters: [{
@@ -1866,6 +1994,16 @@ function between(column, start, end) {
1866
1994
  }]
1867
1995
  };
1868
1996
  }
1997
+ function topLevel(column) {
1998
+ return {
1999
+ _constraints: {},
2000
+ _filters: [{
2001
+ dimension: column.dimension,
2002
+ operator: "topLevel",
2003
+ expression: ""
2004
+ }]
2005
+ };
2006
+ }
1869
2007
 
1870
2008
  //#endregion
1871
2009
  //#region src/query/index.ts
@@ -1877,4 +2015,4 @@ function daysAgo(n) {
1877
2015
  }
1878
2016
 
1879
2017
  //#endregion
1880
- export { Countries, Devices, SearchTypes, and, between, contains, country, currentPstDate, date, dayjs, dayjsPst, daysAgo, device, eq, extractDateRange, gsc, gt, gte, inArray, like, lt, lte, ne, not, notRegex, or, page, query, regex, searchAppearance, searchType, today };
2018
+ export { Countries, Devices, SearchTypes, and, between, clicks, contains, country, ctr, currentPstDate, date, dayjs, dayjsPst, daysAgo, device, eq, extractDateRange, extractMetricFilters, extractSpecialOperatorFilters, gsc, gt, gte, impressions, inArray, isJsonFilter, like, lt, lte, ne, not, notRegex, or, page, parseJsonFilter, position, query, queryCanonical, regex, searchAppearance, searchType, today, topLevel };
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "gscdump",
3
3
  "type": "module",
4
- "version": "0.2.0",
4
+ "version": "0.3.1",
5
5
  "description": "Google Search Console API wrapper with typed query builder, streaming pagination, and SEO analysis functions",
6
6
  "author": {
7
7
  "name": "Harlan Wilton",