gscdump 0.2.0 → 0.3.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- 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 +54 -6
- package/dist/query/index.mjs +148 -10
- 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,47 @@ type GSCRow<D extends Dimension[], C> = { [K in D[number]]: K extends keyof C ?
|
|
|
81
84
|
ctr: number;
|
|
82
85
|
position: number;
|
|
83
86
|
};
|
|
87
|
+
type Metric = 'clicks' | 'impressions' | 'ctr' | 'position';
|
|
88
|
+
declare const MetricColumnBrand: unique symbol;
|
|
89
|
+
interface MetricColumn<M extends Metric> {
|
|
90
|
+
readonly [MetricColumnBrand]: M;
|
|
91
|
+
readonly metric: M;
|
|
92
|
+
}
|
|
84
93
|
interface BuilderState {
|
|
85
94
|
dimensions: Dimension[];
|
|
95
|
+
metrics?: Metric[];
|
|
86
96
|
filter?: Filter<any>;
|
|
97
|
+
orderBy?: {
|
|
98
|
+
column: Metric | 'date';
|
|
99
|
+
dir: 'asc' | 'desc';
|
|
100
|
+
};
|
|
87
101
|
rowLimit?: number;
|
|
88
102
|
startRow?: number;
|
|
89
103
|
}
|
|
104
|
+
interface JsonInternalFilter {
|
|
105
|
+
dimension: string;
|
|
106
|
+
operator: string;
|
|
107
|
+
expression: string;
|
|
108
|
+
expression2?: string;
|
|
109
|
+
}
|
|
110
|
+
interface JsonFilter {
|
|
111
|
+
_filters: JsonInternalFilter[];
|
|
112
|
+
_nestedGroups?: JsonFilter[];
|
|
113
|
+
_groupType?: 'and' | 'or';
|
|
114
|
+
}
|
|
115
|
+
type FilterInput = Filter<any> | JsonFilter;
|
|
90
116
|
//#endregion
|
|
91
117
|
//#region src/query/builder.d.ts
|
|
118
|
+
type SelectableColumn = Column<Dimension> | MetricColumn<Metric>;
|
|
119
|
+
type OrderableColumn = MetricColumn<Metric> | Column<'date'>;
|
|
120
|
+
type ExtractDimensions<T extends SelectableColumn[]> = { [K in keyof T]: T[K] extends Column<infer D> ? D : never }[number] extends infer U ? Exclude<U, never>[] : never;
|
|
92
121
|
interface GSCQueryBuilder<D extends Dimension[] = [], C = object> {
|
|
93
|
-
select:
|
|
122
|
+
select: {
|
|
123
|
+
<T extends Dimension[]>(...dims: T): GSCQueryBuilder<T, C>;
|
|
124
|
+
<T extends SelectableColumn[]>(...cols: T): GSCQueryBuilder<ExtractDimensions<T> & Dimension[], C>;
|
|
125
|
+
};
|
|
94
126
|
where: <F extends Filter<any>>(filter: F) => GSCQueryBuilder<D, C & F['_constraints']>;
|
|
127
|
+
orderBy: (col: OrderableColumn, dir: 'asc' | 'desc') => GSCQueryBuilder<D, C>;
|
|
95
128
|
limit: (n: number) => GSCQueryBuilder<D, C>;
|
|
96
129
|
offset: (n: number) => GSCQueryBuilder<D, C>;
|
|
97
130
|
toBody: () => SearchAnalyticsQuery;
|
|
@@ -102,10 +135,15 @@ declare const gsc: GSCQueryBuilder<[], object>;
|
|
|
102
135
|
//#region src/query/columns.d.ts
|
|
103
136
|
declare const page: Column<"page">;
|
|
104
137
|
declare const query: Column<"query">;
|
|
138
|
+
declare const queryCanonical: Column<"queryCanonical">;
|
|
105
139
|
declare const device: Column<"device">;
|
|
106
140
|
declare const country: Column<"country">;
|
|
107
141
|
declare const searchAppearance: Column<"searchAppearance">;
|
|
108
142
|
declare const date: Column<"date">;
|
|
143
|
+
declare const clicks: MetricColumn<"clicks">;
|
|
144
|
+
declare const impressions: MetricColumn<"impressions">;
|
|
145
|
+
declare const ctr: MetricColumn<"ctr">;
|
|
146
|
+
declare const position: MetricColumn<"position">;
|
|
109
147
|
declare const searchType: QueryParam<"searchType">;
|
|
110
148
|
//#endregion
|
|
111
149
|
//#region src/query/operators.d.ts
|
|
@@ -120,17 +158,27 @@ declare function notRegex<D extends Dimension>(column: Column<D>, pattern: RegEx
|
|
|
120
158
|
declare function and<F extends Filter<any>[]>(...filters: F): Filter<MergeConstraints<F>>;
|
|
121
159
|
declare function or<F extends Filter<any>[]>(...filters: F): Filter<object>;
|
|
122
160
|
declare function not<F extends Filter<any>>(filter: F): Filter<object>;
|
|
161
|
+
declare function gte<M extends Metric>(column: MetricColumn<M>, value: number): Filter<object>;
|
|
123
162
|
declare function gte<D extends Dimension>(column: Column<D>, value: DimensionValueMap[D]): Filter<object>;
|
|
163
|
+
declare function gt<M extends Metric>(column: MetricColumn<M>, value: number): Filter<object>;
|
|
124
164
|
declare function gt<D extends Dimension>(column: Column<D>, value: DimensionValueMap[D]): Filter<object>;
|
|
165
|
+
declare function lte<M extends Metric>(column: MetricColumn<M>, value: number): Filter<object>;
|
|
125
166
|
declare function lte<D extends Dimension>(column: Column<D>, value: DimensionValueMap[D]): Filter<object>;
|
|
167
|
+
declare function lt<M extends Metric>(column: MetricColumn<M>, value: number): Filter<object>;
|
|
126
168
|
declare function lt<D extends Dimension>(column: Column<D>, value: DimensionValueMap[D]): Filter<object>;
|
|
169
|
+
declare function between<M extends Metric>(column: MetricColumn<M>, start: number, end: number): Filter<object>;
|
|
127
170
|
declare function between<D extends Dimension>(column: Column<D>, start: DimensionValueMap[D], end: DimensionValueMap[D]): Filter<object>;
|
|
171
|
+
declare function topLevel(column: Column<'page'>): Filter<object>;
|
|
128
172
|
//#endregion
|
|
129
173
|
//#region src/query/resolver.d.ts
|
|
130
|
-
declare function
|
|
174
|
+
declare function isJsonFilter(value: unknown): value is JsonFilter;
|
|
175
|
+
declare function parseJsonFilter(json: JsonFilter): Filter<any>;
|
|
176
|
+
declare function extractDateRange(input?: FilterInput): {
|
|
131
177
|
startDate?: string;
|
|
132
178
|
endDate?: string;
|
|
133
179
|
};
|
|
180
|
+
declare function extractMetricFilters(input?: FilterInput): InternalFilter[];
|
|
181
|
+
declare function extractSpecialOperatorFilters(input?: FilterInput): InternalFilter[];
|
|
134
182
|
//#endregion
|
|
135
183
|
//#region src/query/utils/dayjs.d.ts
|
|
136
184
|
declare function dayjs(date?: _dayjs.ConfigType): Dayjs;
|
|
@@ -141,4 +189,4 @@ declare function dayjsPst(): Dayjs;
|
|
|
141
189
|
declare function today(): string;
|
|
142
190
|
declare function daysAgo(n: number): string;
|
|
143
191
|
//#endregion
|
|
144
|
-
export { type BuilderState, type Column, Countries, type Country, type Device, Devices, type Dimension, type DimensionValueMap, type Filter, type GSCQueryBuilder, type GSCResult, type GSCRow, type QueryParam, type QueryParamName, type QueryParamValueMap, type SearchType, SearchTypes, and, between, contains, country, currentPstDate, date, dayjs, dayjsPst, daysAgo, device, eq, extractDateRange, gsc, gt, gte, inArray, like, lt, lte, ne, not, notRegex, or, page, query, regex, searchAppearance, searchType, today };
|
|
192
|
+
export { type BuilderState, type Column, Countries, type Country, type Device, Devices, type Dimension, type DimensionValueMap, type Filter, type FilterInput, type GSCQueryBuilder, type GSCResult, type GSCRow, type InternalFilter, type JsonFilter, type JsonInternalFilter, type Metric, type MetricColumn, type QueryParam, type QueryParamName, type QueryParamValueMap, type SearchType, SearchTypes, and, between, clicks, contains, country, ctr, currentPstDate, date, dayjs, dayjsPst, daysAgo, device, eq, extractDateRange, extractMetricFilters, extractSpecialOperatorFilters, gsc, gt, gte, impressions, inArray, isJsonFilter, like, lt, lte, ne, not, notRegex, or, page, parseJsonFilter, position, query, queryCanonical, regex, searchAppearance, searchType, today, topLevel };
|
package/dist/query/index.mjs
CHANGED
|
@@ -24,7 +24,41 @@ const DATE_OPERATORS = [
|
|
|
24
24
|
"lt",
|
|
25
25
|
"between"
|
|
26
26
|
];
|
|
27
|
+
const METRIC_OPERATORS = [
|
|
28
|
+
"metricGte",
|
|
29
|
+
"metricGt",
|
|
30
|
+
"metricLte",
|
|
31
|
+
"metricLt",
|
|
32
|
+
"metricBetween"
|
|
33
|
+
];
|
|
34
|
+
const SPECIAL_OPERATORS = ["topLevel"];
|
|
27
35
|
const QUERY_PARAMS = ["searchType"];
|
|
36
|
+
function isJsonFilter(value) {
|
|
37
|
+
return typeof value === "object" && value !== null && "_filters" in value && Array.isArray(value._filters);
|
|
38
|
+
}
|
|
39
|
+
function parseJsonFilter(json) {
|
|
40
|
+
return {
|
|
41
|
+
_constraints: {},
|
|
42
|
+
_filters: json._filters.map((f) => ({
|
|
43
|
+
dimension: f.dimension,
|
|
44
|
+
operator: f.operator,
|
|
45
|
+
expression: f.expression,
|
|
46
|
+
expression2: f.expression2
|
|
47
|
+
})),
|
|
48
|
+
_nestedGroups: json._nestedGroups?.map(parseJsonFilter),
|
|
49
|
+
_groupType: json._groupType
|
|
50
|
+
};
|
|
51
|
+
}
|
|
52
|
+
function normalizeFilter(input) {
|
|
53
|
+
if (!input) return void 0;
|
|
54
|
+
return input;
|
|
55
|
+
}
|
|
56
|
+
function isMetricOperator(op) {
|
|
57
|
+
return METRIC_OPERATORS.includes(op);
|
|
58
|
+
}
|
|
59
|
+
function isSpecialOperator(op) {
|
|
60
|
+
return SPECIAL_OPERATORS.includes(op);
|
|
61
|
+
}
|
|
28
62
|
function isDateOperator(op) {
|
|
29
63
|
return DATE_OPERATORS.includes(op);
|
|
30
64
|
}
|
|
@@ -63,7 +97,8 @@ function extractSpecialFilters(filter) {
|
|
|
63
97
|
}
|
|
64
98
|
else if (isQueryParam(f.dimension)) {
|
|
65
99
|
if (f.dimension === "searchType") searchType$1 = f.expression;
|
|
66
|
-
} else otherFilters.push(f);
|
|
100
|
+
} else if (isMetricOperator(f.operator) || isSpecialOperator(f.operator)) otherFilters.push(f);
|
|
101
|
+
else otherFilters.push(f);
|
|
67
102
|
if (filter._nestedGroups) for (const nested of filter._nestedGroups) {
|
|
68
103
|
const extracted = extractSpecialFilters(nested);
|
|
69
104
|
if (extracted.startDate) startDate = extracted.startDate;
|
|
@@ -83,13 +118,27 @@ function extractSpecialFilters(filter) {
|
|
|
83
118
|
dimensionFilter
|
|
84
119
|
};
|
|
85
120
|
}
|
|
86
|
-
function extractDateRange(
|
|
87
|
-
const { startDate, endDate } = extractSpecialFilters(
|
|
121
|
+
function extractDateRange(input) {
|
|
122
|
+
const { startDate, endDate } = extractSpecialFilters(normalizeFilter(input));
|
|
88
123
|
return {
|
|
89
124
|
startDate,
|
|
90
125
|
endDate
|
|
91
126
|
};
|
|
92
127
|
}
|
|
128
|
+
function extractMetricFilters(input) {
|
|
129
|
+
const filter = normalizeFilter(input);
|
|
130
|
+
if (!filter) return [];
|
|
131
|
+
const metricFilters = filter._filters.filter((f) => isMetricOperator(f.operator));
|
|
132
|
+
const nested = filter._nestedGroups?.flatMap((g) => extractMetricFilters(g)) ?? [];
|
|
133
|
+
return [...metricFilters, ...nested];
|
|
134
|
+
}
|
|
135
|
+
function extractSpecialOperatorFilters(input) {
|
|
136
|
+
const filter = normalizeFilter(input);
|
|
137
|
+
if (!filter) return [];
|
|
138
|
+
const special = filter._filters.filter((f) => isSpecialOperator(f.operator));
|
|
139
|
+
const nested = filter._nestedGroups?.flatMap((g) => extractSpecialOperatorFilters(g)) ?? [];
|
|
140
|
+
return [...special, ...nested];
|
|
141
|
+
}
|
|
93
142
|
function resolveToBody(state) {
|
|
94
143
|
const { startDate, endDate, searchType: searchType$1, dimensionFilter } = extractSpecialFilters(state.filter);
|
|
95
144
|
if (!startDate || !endDate) throw new Error("Date range required: use .where(between(date, start, end)) or .where(and(gte(date, start), lte(date, end)))");
|
|
@@ -105,19 +154,24 @@ function resolveToBody(state) {
|
|
|
105
154
|
if (filterGroups.length > 0) body.dimensionFilterGroups = filterGroups;
|
|
106
155
|
return body;
|
|
107
156
|
}
|
|
157
|
+
function isApiFilter(f) {
|
|
158
|
+
return !isMetricOperator(f.operator) && !isSpecialOperator(f.operator);
|
|
159
|
+
}
|
|
108
160
|
function resolveFilter(filter) {
|
|
109
161
|
if (!filter) return [];
|
|
110
162
|
const groups = [];
|
|
111
|
-
|
|
112
|
-
|
|
163
|
+
const groupType = filter._groupType ?? "and";
|
|
164
|
+
const apiFilters = filter._filters.filter(isApiFilter);
|
|
165
|
+
if (groupType === "or") {
|
|
166
|
+
if (apiFilters.length > 0) groups.push({
|
|
113
167
|
groupType: "or",
|
|
114
|
-
filters:
|
|
168
|
+
filters: apiFilters.map((f) => ({
|
|
115
169
|
dimension: f.dimension,
|
|
116
170
|
operator: f.operator,
|
|
117
171
|
expression: f.expression
|
|
118
172
|
}))
|
|
119
173
|
});
|
|
120
|
-
} else if (
|
|
174
|
+
} else if (apiFilters.length > 0) groups.push({ filters: apiFilters.map((f) => ({
|
|
121
175
|
dimension: f.dimension,
|
|
122
176
|
operator: f.operator,
|
|
123
177
|
expression: f.expression
|
|
@@ -128,12 +182,27 @@ function resolveFilter(filter) {
|
|
|
128
182
|
|
|
129
183
|
//#endregion
|
|
130
184
|
//#region src/query/builder.ts
|
|
185
|
+
function isDimensionString(v) {
|
|
186
|
+
return typeof v === "string";
|
|
187
|
+
}
|
|
188
|
+
function isMetricColumn(v) {
|
|
189
|
+
return typeof v === "object" && v !== null && "metric" in v;
|
|
190
|
+
}
|
|
191
|
+
function isDimensionColumn(v) {
|
|
192
|
+
return typeof v === "object" && v !== null && "dimension" in v && !("metric" in v);
|
|
193
|
+
}
|
|
131
194
|
function createBuilder(state) {
|
|
132
195
|
return {
|
|
133
|
-
select(...
|
|
196
|
+
select(...args) {
|
|
197
|
+
const dimensions = [];
|
|
198
|
+
const metrics = [];
|
|
199
|
+
for (const arg of args) if (isDimensionString(arg)) dimensions.push(arg);
|
|
200
|
+
else if (isDimensionColumn(arg)) dimensions.push(arg.dimension);
|
|
201
|
+
else if (isMetricColumn(arg)) metrics.push(arg.metric);
|
|
134
202
|
return createBuilder({
|
|
135
203
|
...state,
|
|
136
|
-
dimensions
|
|
204
|
+
dimensions,
|
|
205
|
+
metrics: metrics.length > 0 ? metrics : void 0
|
|
137
206
|
});
|
|
138
207
|
},
|
|
139
208
|
where(filter) {
|
|
@@ -142,6 +211,16 @@ function createBuilder(state) {
|
|
|
142
211
|
filter
|
|
143
212
|
});
|
|
144
213
|
},
|
|
214
|
+
orderBy(col, dir) {
|
|
215
|
+
const column = isMetricColumn(col) ? col.metric : col.dimension;
|
|
216
|
+
return createBuilder({
|
|
217
|
+
...state,
|
|
218
|
+
orderBy: {
|
|
219
|
+
column,
|
|
220
|
+
dir
|
|
221
|
+
}
|
|
222
|
+
});
|
|
223
|
+
},
|
|
145
224
|
limit(n) {
|
|
146
225
|
return createBuilder({
|
|
147
226
|
...state,
|
|
@@ -169,15 +248,23 @@ const gsc = createBuilder({ dimensions: [] });
|
|
|
169
248
|
function createColumn(dimension) {
|
|
170
249
|
return { dimension };
|
|
171
250
|
}
|
|
251
|
+
function createMetricColumn(metric) {
|
|
252
|
+
return { metric };
|
|
253
|
+
}
|
|
172
254
|
function createQueryParam(param) {
|
|
173
255
|
return { param };
|
|
174
256
|
}
|
|
175
257
|
const page = createColumn("page");
|
|
176
258
|
const query = createColumn("query");
|
|
259
|
+
const queryCanonical = createColumn("queryCanonical");
|
|
177
260
|
const device = createColumn("device");
|
|
178
261
|
const country = createColumn("country");
|
|
179
262
|
const searchAppearance = createColumn("searchAppearance");
|
|
180
263
|
const date = createColumn("date");
|
|
264
|
+
const clicks = createMetricColumn("clicks");
|
|
265
|
+
const impressions = createMetricColumn("impressions");
|
|
266
|
+
const ctr = createMetricColumn("ctr");
|
|
267
|
+
const position = createMetricColumn("position");
|
|
181
268
|
const searchType = createQueryParam("searchType");
|
|
182
269
|
|
|
183
270
|
//#endregion
|
|
@@ -1816,6 +1903,14 @@ function invertOperator(op) {
|
|
|
1816
1903
|
}[op];
|
|
1817
1904
|
}
|
|
1818
1905
|
function gte(column, value) {
|
|
1906
|
+
if ("metric" in column) return {
|
|
1907
|
+
_constraints: {},
|
|
1908
|
+
_filters: [{
|
|
1909
|
+
dimension: column.metric,
|
|
1910
|
+
operator: "metricGte",
|
|
1911
|
+
expression: String(value)
|
|
1912
|
+
}]
|
|
1913
|
+
};
|
|
1819
1914
|
return {
|
|
1820
1915
|
_constraints: {},
|
|
1821
1916
|
_filters: [{
|
|
@@ -1826,6 +1921,14 @@ function gte(column, value) {
|
|
|
1826
1921
|
};
|
|
1827
1922
|
}
|
|
1828
1923
|
function gt(column, value) {
|
|
1924
|
+
if ("metric" in column) return {
|
|
1925
|
+
_constraints: {},
|
|
1926
|
+
_filters: [{
|
|
1927
|
+
dimension: column.metric,
|
|
1928
|
+
operator: "metricGt",
|
|
1929
|
+
expression: String(value)
|
|
1930
|
+
}]
|
|
1931
|
+
};
|
|
1829
1932
|
return {
|
|
1830
1933
|
_constraints: {},
|
|
1831
1934
|
_filters: [{
|
|
@@ -1836,6 +1939,14 @@ function gt(column, value) {
|
|
|
1836
1939
|
};
|
|
1837
1940
|
}
|
|
1838
1941
|
function lte(column, value) {
|
|
1942
|
+
if ("metric" in column) return {
|
|
1943
|
+
_constraints: {},
|
|
1944
|
+
_filters: [{
|
|
1945
|
+
dimension: column.metric,
|
|
1946
|
+
operator: "metricLte",
|
|
1947
|
+
expression: String(value)
|
|
1948
|
+
}]
|
|
1949
|
+
};
|
|
1839
1950
|
return {
|
|
1840
1951
|
_constraints: {},
|
|
1841
1952
|
_filters: [{
|
|
@@ -1846,6 +1957,14 @@ function lte(column, value) {
|
|
|
1846
1957
|
};
|
|
1847
1958
|
}
|
|
1848
1959
|
function lt(column, value) {
|
|
1960
|
+
if ("metric" in column) return {
|
|
1961
|
+
_constraints: {},
|
|
1962
|
+
_filters: [{
|
|
1963
|
+
dimension: column.metric,
|
|
1964
|
+
operator: "metricLt",
|
|
1965
|
+
expression: String(value)
|
|
1966
|
+
}]
|
|
1967
|
+
};
|
|
1849
1968
|
return {
|
|
1850
1969
|
_constraints: {},
|
|
1851
1970
|
_filters: [{
|
|
@@ -1856,6 +1975,15 @@ function lt(column, value) {
|
|
|
1856
1975
|
};
|
|
1857
1976
|
}
|
|
1858
1977
|
function between(column, start, end) {
|
|
1978
|
+
if ("metric" in column) return {
|
|
1979
|
+
_constraints: {},
|
|
1980
|
+
_filters: [{
|
|
1981
|
+
dimension: column.metric,
|
|
1982
|
+
operator: "metricBetween",
|
|
1983
|
+
expression: String(start),
|
|
1984
|
+
expression2: String(end)
|
|
1985
|
+
}]
|
|
1986
|
+
};
|
|
1859
1987
|
return {
|
|
1860
1988
|
_constraints: {},
|
|
1861
1989
|
_filters: [{
|
|
@@ -1866,6 +1994,16 @@ function between(column, start, end) {
|
|
|
1866
1994
|
}]
|
|
1867
1995
|
};
|
|
1868
1996
|
}
|
|
1997
|
+
function topLevel(column) {
|
|
1998
|
+
return {
|
|
1999
|
+
_constraints: {},
|
|
2000
|
+
_filters: [{
|
|
2001
|
+
dimension: column.dimension,
|
|
2002
|
+
operator: "topLevel",
|
|
2003
|
+
expression: ""
|
|
2004
|
+
}]
|
|
2005
|
+
};
|
|
2006
|
+
}
|
|
1869
2007
|
|
|
1870
2008
|
//#endregion
|
|
1871
2009
|
//#region src/query/index.ts
|
|
@@ -1877,4 +2015,4 @@ function daysAgo(n) {
|
|
|
1877
2015
|
}
|
|
1878
2016
|
|
|
1879
2017
|
//#endregion
|
|
1880
|
-
export { Countries, Devices, SearchTypes, and, between, contains, country, currentPstDate, date, dayjs, dayjsPst, daysAgo, device, eq, extractDateRange, gsc, gt, gte, inArray, like, lt, lte, ne, not, notRegex, or, page, query, regex, searchAppearance, searchType, today };
|
|
2018
|
+
export { Countries, Devices, SearchTypes, and, between, clicks, contains, country, ctr, currentPstDate, date, dayjs, dayjsPst, daysAgo, device, eq, extractDateRange, extractMetricFilters, extractSpecialOperatorFilters, gsc, gt, gte, impressions, inArray, isJsonFilter, like, lt, lte, ne, not, notRegex, or, page, parseJsonFilter, position, query, queryCanonical, regex, searchAppearance, searchType, today, topLevel };
|
package/package.json
CHANGED