gscdump 0.2.0 → 0.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.
@@ -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,35 @@ 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
  }
90
104
  //#endregion
91
105
  //#region src/query/builder.d.ts
106
+ type SelectableColumn = Column<Dimension> | MetricColumn<Metric>;
107
+ type OrderableColumn = MetricColumn<Metric> | Column<'date'>;
108
+ 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
109
  interface GSCQueryBuilder<D extends Dimension[] = [], C = object> {
93
- select: <T extends Dimension[]>(...dims: T) => GSCQueryBuilder<T, C>;
110
+ select: {
111
+ <T extends Dimension[]>(...dims: T): GSCQueryBuilder<T, C>;
112
+ <T extends SelectableColumn[]>(...cols: T): GSCQueryBuilder<ExtractDimensions<T> & Dimension[], C>;
113
+ };
94
114
  where: <F extends Filter<any>>(filter: F) => GSCQueryBuilder<D, C & F['_constraints']>;
115
+ orderBy: (col: OrderableColumn, dir: 'asc' | 'desc') => GSCQueryBuilder<D, C>;
95
116
  limit: (n: number) => GSCQueryBuilder<D, C>;
96
117
  offset: (n: number) => GSCQueryBuilder<D, C>;
97
118
  toBody: () => SearchAnalyticsQuery;
@@ -102,10 +123,15 @@ declare const gsc: GSCQueryBuilder<[], object>;
102
123
  //#region src/query/columns.d.ts
103
124
  declare const page: Column<"page">;
104
125
  declare const query: Column<"query">;
126
+ declare const queryCanonical: Column<"queryCanonical">;
105
127
  declare const device: Column<"device">;
106
128
  declare const country: Column<"country">;
107
129
  declare const searchAppearance: Column<"searchAppearance">;
108
130
  declare const date: Column<"date">;
131
+ declare const clicks: MetricColumn<"clicks">;
132
+ declare const impressions: MetricColumn<"impressions">;
133
+ declare const ctr: MetricColumn<"ctr">;
134
+ declare const position: MetricColumn<"position">;
109
135
  declare const searchType: QueryParam<"searchType">;
110
136
  //#endregion
111
137
  //#region src/query/operators.d.ts
@@ -120,17 +146,25 @@ declare function notRegex<D extends Dimension>(column: Column<D>, pattern: RegEx
120
146
  declare function and<F extends Filter<any>[]>(...filters: F): Filter<MergeConstraints<F>>;
121
147
  declare function or<F extends Filter<any>[]>(...filters: F): Filter<object>;
122
148
  declare function not<F extends Filter<any>>(filter: F): Filter<object>;
149
+ declare function gte<M extends Metric>(column: MetricColumn<M>, value: number): Filter<object>;
123
150
  declare function gte<D extends Dimension>(column: Column<D>, value: DimensionValueMap[D]): Filter<object>;
151
+ declare function gt<M extends Metric>(column: MetricColumn<M>, value: number): Filter<object>;
124
152
  declare function gt<D extends Dimension>(column: Column<D>, value: DimensionValueMap[D]): Filter<object>;
153
+ declare function lte<M extends Metric>(column: MetricColumn<M>, value: number): Filter<object>;
125
154
  declare function lte<D extends Dimension>(column: Column<D>, value: DimensionValueMap[D]): Filter<object>;
155
+ declare function lt<M extends Metric>(column: MetricColumn<M>, value: number): Filter<object>;
126
156
  declare function lt<D extends Dimension>(column: Column<D>, value: DimensionValueMap[D]): Filter<object>;
157
+ declare function between<M extends Metric>(column: MetricColumn<M>, start: number, end: number): Filter<object>;
127
158
  declare function between<D extends Dimension>(column: Column<D>, start: DimensionValueMap[D], end: DimensionValueMap[D]): Filter<object>;
159
+ declare function topLevel(column: Column<'page'>): Filter<object>;
128
160
  //#endregion
129
161
  //#region src/query/resolver.d.ts
130
162
  declare function extractDateRange(filter?: Filter<any>): {
131
163
  startDate?: string;
132
164
  endDate?: string;
133
165
  };
166
+ declare function extractMetricFilters(filter?: Filter<any>): InternalFilter[];
167
+ declare function extractSpecialOperatorFilters(filter?: Filter<any>): InternalFilter[];
134
168
  //#endregion
135
169
  //#region src/query/utils/dayjs.d.ts
136
170
  declare function dayjs(date?: _dayjs.ConfigType): Dayjs;
@@ -141,4 +175,4 @@ declare function dayjsPst(): Dayjs;
141
175
  declare function today(): string;
142
176
  declare function daysAgo(n: number): string;
143
177
  //#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 };
178
+ export { type BuilderState, type Column, Countries, type Country, type Device, Devices, type Dimension, type DimensionValueMap, type Filter, type GSCQueryBuilder, type GSCResult, type GSCRow, type InternalFilter, 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, like, lt, lte, ne, not, notRegex, or, page, position, query, queryCanonical, regex, searchAppearance, searchType, today, topLevel };
@@ -24,7 +24,21 @@ 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 isMetricOperator(op) {
37
+ return METRIC_OPERATORS.includes(op);
38
+ }
39
+ function isSpecialOperator(op) {
40
+ return SPECIAL_OPERATORS.includes(op);
41
+ }
28
42
  function isDateOperator(op) {
29
43
  return DATE_OPERATORS.includes(op);
30
44
  }
@@ -63,7 +77,8 @@ function extractSpecialFilters(filter) {
63
77
  }
64
78
  else if (isQueryParam(f.dimension)) {
65
79
  if (f.dimension === "searchType") searchType$1 = f.expression;
66
- } else otherFilters.push(f);
80
+ } else if (isMetricOperator(f.operator) || isSpecialOperator(f.operator)) otherFilters.push(f);
81
+ else otherFilters.push(f);
67
82
  if (filter._nestedGroups) for (const nested of filter._nestedGroups) {
68
83
  const extracted = extractSpecialFilters(nested);
69
84
  if (extracted.startDate) startDate = extracted.startDate;
@@ -90,6 +105,18 @@ function extractDateRange(filter) {
90
105
  endDate
91
106
  };
92
107
  }
108
+ function extractMetricFilters(filter) {
109
+ if (!filter) return [];
110
+ const metricFilters = filter._filters.filter((f) => isMetricOperator(f.operator));
111
+ const nested = filter._nestedGroups?.flatMap((g) => extractMetricFilters(g)) ?? [];
112
+ return [...metricFilters, ...nested];
113
+ }
114
+ function extractSpecialOperatorFilters(filter) {
115
+ if (!filter) return [];
116
+ const special = filter._filters.filter((f) => isSpecialOperator(f.operator));
117
+ const nested = filter._nestedGroups?.flatMap((g) => extractSpecialOperatorFilters(g)) ?? [];
118
+ return [...special, ...nested];
119
+ }
93
120
  function resolveToBody(state) {
94
121
  const { startDate, endDate, searchType: searchType$1, dimensionFilter } = extractSpecialFilters(state.filter);
95
122
  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 +132,24 @@ function resolveToBody(state) {
105
132
  if (filterGroups.length > 0) body.dimensionFilterGroups = filterGroups;
106
133
  return body;
107
134
  }
135
+ function isApiFilter(f) {
136
+ return !isMetricOperator(f.operator) && !isSpecialOperator(f.operator);
137
+ }
108
138
  function resolveFilter(filter) {
109
139
  if (!filter) return [];
110
140
  const groups = [];
111
- if ((filter._groupType ?? "and") === "or") {
112
- if (filter._filters.length > 0) groups.push({
141
+ const groupType = filter._groupType ?? "and";
142
+ const apiFilters = filter._filters.filter(isApiFilter);
143
+ if (groupType === "or") {
144
+ if (apiFilters.length > 0) groups.push({
113
145
  groupType: "or",
114
- filters: filter._filters.map((f) => ({
146
+ filters: apiFilters.map((f) => ({
115
147
  dimension: f.dimension,
116
148
  operator: f.operator,
117
149
  expression: f.expression
118
150
  }))
119
151
  });
120
- } else if (filter._filters.length > 0) groups.push({ filters: filter._filters.map((f) => ({
152
+ } else if (apiFilters.length > 0) groups.push({ filters: apiFilters.map((f) => ({
121
153
  dimension: f.dimension,
122
154
  operator: f.operator,
123
155
  expression: f.expression
@@ -128,12 +160,27 @@ function resolveFilter(filter) {
128
160
 
129
161
  //#endregion
130
162
  //#region src/query/builder.ts
163
+ function isDimensionString(v) {
164
+ return typeof v === "string";
165
+ }
166
+ function isMetricColumn(v) {
167
+ return typeof v === "object" && v !== null && "metric" in v;
168
+ }
169
+ function isDimensionColumn(v) {
170
+ return typeof v === "object" && v !== null && "dimension" in v && !("metric" in v);
171
+ }
131
172
  function createBuilder(state) {
132
173
  return {
133
- select(...dims) {
174
+ select(...args) {
175
+ const dimensions = [];
176
+ const metrics = [];
177
+ for (const arg of args) if (isDimensionString(arg)) dimensions.push(arg);
178
+ else if (isDimensionColumn(arg)) dimensions.push(arg.dimension);
179
+ else if (isMetricColumn(arg)) metrics.push(arg.metric);
134
180
  return createBuilder({
135
181
  ...state,
136
- dimensions: dims
182
+ dimensions,
183
+ metrics: metrics.length > 0 ? metrics : void 0
137
184
  });
138
185
  },
139
186
  where(filter) {
@@ -142,6 +189,16 @@ function createBuilder(state) {
142
189
  filter
143
190
  });
144
191
  },
192
+ orderBy(col, dir) {
193
+ const column = isMetricColumn(col) ? col.metric : col.dimension;
194
+ return createBuilder({
195
+ ...state,
196
+ orderBy: {
197
+ column,
198
+ dir
199
+ }
200
+ });
201
+ },
145
202
  limit(n) {
146
203
  return createBuilder({
147
204
  ...state,
@@ -169,15 +226,23 @@ const gsc = createBuilder({ dimensions: [] });
169
226
  function createColumn(dimension) {
170
227
  return { dimension };
171
228
  }
229
+ function createMetricColumn(metric) {
230
+ return { metric };
231
+ }
172
232
  function createQueryParam(param) {
173
233
  return { param };
174
234
  }
175
235
  const page = createColumn("page");
176
236
  const query = createColumn("query");
237
+ const queryCanonical = createColumn("queryCanonical");
177
238
  const device = createColumn("device");
178
239
  const country = createColumn("country");
179
240
  const searchAppearance = createColumn("searchAppearance");
180
241
  const date = createColumn("date");
242
+ const clicks = createMetricColumn("clicks");
243
+ const impressions = createMetricColumn("impressions");
244
+ const ctr = createMetricColumn("ctr");
245
+ const position = createMetricColumn("position");
181
246
  const searchType = createQueryParam("searchType");
182
247
 
183
248
  //#endregion
@@ -1816,6 +1881,14 @@ function invertOperator(op) {
1816
1881
  }[op];
1817
1882
  }
1818
1883
  function gte(column, value) {
1884
+ if ("metric" in column) return {
1885
+ _constraints: {},
1886
+ _filters: [{
1887
+ dimension: column.metric,
1888
+ operator: "metricGte",
1889
+ expression: String(value)
1890
+ }]
1891
+ };
1819
1892
  return {
1820
1893
  _constraints: {},
1821
1894
  _filters: [{
@@ -1826,6 +1899,14 @@ function gte(column, value) {
1826
1899
  };
1827
1900
  }
1828
1901
  function gt(column, value) {
1902
+ if ("metric" in column) return {
1903
+ _constraints: {},
1904
+ _filters: [{
1905
+ dimension: column.metric,
1906
+ operator: "metricGt",
1907
+ expression: String(value)
1908
+ }]
1909
+ };
1829
1910
  return {
1830
1911
  _constraints: {},
1831
1912
  _filters: [{
@@ -1836,6 +1917,14 @@ function gt(column, value) {
1836
1917
  };
1837
1918
  }
1838
1919
  function lte(column, value) {
1920
+ if ("metric" in column) return {
1921
+ _constraints: {},
1922
+ _filters: [{
1923
+ dimension: column.metric,
1924
+ operator: "metricLte",
1925
+ expression: String(value)
1926
+ }]
1927
+ };
1839
1928
  return {
1840
1929
  _constraints: {},
1841
1930
  _filters: [{
@@ -1846,6 +1935,14 @@ function lte(column, value) {
1846
1935
  };
1847
1936
  }
1848
1937
  function lt(column, value) {
1938
+ if ("metric" in column) return {
1939
+ _constraints: {},
1940
+ _filters: [{
1941
+ dimension: column.metric,
1942
+ operator: "metricLt",
1943
+ expression: String(value)
1944
+ }]
1945
+ };
1849
1946
  return {
1850
1947
  _constraints: {},
1851
1948
  _filters: [{
@@ -1856,6 +1953,15 @@ function lt(column, value) {
1856
1953
  };
1857
1954
  }
1858
1955
  function between(column, start, end) {
1956
+ if ("metric" in column) return {
1957
+ _constraints: {},
1958
+ _filters: [{
1959
+ dimension: column.metric,
1960
+ operator: "metricBetween",
1961
+ expression: String(start),
1962
+ expression2: String(end)
1963
+ }]
1964
+ };
1859
1965
  return {
1860
1966
  _constraints: {},
1861
1967
  _filters: [{
@@ -1866,6 +1972,16 @@ function between(column, start, end) {
1866
1972
  }]
1867
1973
  };
1868
1974
  }
1975
+ function topLevel(column) {
1976
+ return {
1977
+ _constraints: {},
1978
+ _filters: [{
1979
+ dimension: column.dimension,
1980
+ operator: "topLevel",
1981
+ expression: ""
1982
+ }]
1983
+ };
1984
+ }
1869
1985
 
1870
1986
  //#endregion
1871
1987
  //#region src/query/index.ts
@@ -1877,4 +1993,4 @@ function daysAgo(n) {
1877
1993
  }
1878
1994
 
1879
1995
  //#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 };
1996
+ 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, like, lt, lte, ne, not, notRegex, or, page, 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.0",
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",