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.
- package/dist/analysis/index.d.mts +29 -3
- package/dist/analysis/index.mjs +120 -58
- package/dist/index.d.mts +53 -4
- package/dist/index.mjs +143 -6
- package/dist/query/index.d.mts +39 -5
- package/dist/query/index.mjs +124 -8
- package/package.json +1 -1
|
@@ -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:
|
|
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;
|
package/dist/analysis/index.mjs
CHANGED
|
@@ -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
|
-
|
|
347
|
-
|
|
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:
|
|
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 (
|
|
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(...
|
|
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
|
|
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:
|
|
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
|
-
|
|
211
|
-
|
|
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:
|
|
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 (
|
|
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 };
|
package/dist/query/index.d.mts
CHANGED
|
@@ -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:
|
|
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 };
|
package/dist/query/index.mjs
CHANGED
|
@@ -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
|
-
|
|
112
|
-
|
|
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:
|
|
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 (
|
|
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(...
|
|
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
|
|
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