gscdump 0.25.14 → 0.26.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/core/result.d.mts +29 -0
- package/dist/core/result.mjs +32 -0
- package/dist/index.d.mts +118 -69
- package/dist/index.mjs +215 -30
- package/dist/query/index.d.mts +120 -4
- package/dist/query/index.mjs +201 -54
- package/dist/query/plan.d.mts +79 -12
- package/dist/query/plan.mjs +164 -39
- package/package.json +7 -2
package/dist/query/plan.mjs
CHANGED
|
@@ -1,3 +1,139 @@
|
|
|
1
|
+
function ok(value) {
|
|
2
|
+
return {
|
|
3
|
+
ok: true,
|
|
4
|
+
value
|
|
5
|
+
};
|
|
6
|
+
}
|
|
7
|
+
function err(error) {
|
|
8
|
+
return {
|
|
9
|
+
ok: false,
|
|
10
|
+
error
|
|
11
|
+
};
|
|
12
|
+
}
|
|
13
|
+
function unwrapResult(result, toError) {
|
|
14
|
+
if (result.ok) return result.value;
|
|
15
|
+
throw toError(result.error);
|
|
16
|
+
}
|
|
17
|
+
const TIME_AXIS_DIMENSIONS$1 = new Set(["date", "hour"]);
|
|
18
|
+
const queryErrors = {
|
|
19
|
+
missingDateRange() {
|
|
20
|
+
return {
|
|
21
|
+
kind: "missing-date-range",
|
|
22
|
+
message: "Date range required: use .where(between(date, start, end)) or .where(and(gte(date, start), lte(date, end)))"
|
|
23
|
+
};
|
|
24
|
+
},
|
|
25
|
+
invalidRowLimit(value) {
|
|
26
|
+
return {
|
|
27
|
+
kind: "invalid-row-limit",
|
|
28
|
+
value,
|
|
29
|
+
message: `rowLimit must be a positive integer, got ${value}`
|
|
30
|
+
};
|
|
31
|
+
},
|
|
32
|
+
invalidStartRow(value) {
|
|
33
|
+
return {
|
|
34
|
+
kind: "invalid-start-row",
|
|
35
|
+
value,
|
|
36
|
+
message: `startRow must be a non-negative integer, got ${value}`
|
|
37
|
+
};
|
|
38
|
+
},
|
|
39
|
+
hourDimensionRequiresHourlyState() {
|
|
40
|
+
return {
|
|
41
|
+
kind: "invalid-data-state",
|
|
42
|
+
message: "hour dimension requires dataState: \"hourly_all\""
|
|
43
|
+
};
|
|
44
|
+
},
|
|
45
|
+
hourlyStateRequiresHourDimension() {
|
|
46
|
+
return {
|
|
47
|
+
kind: "invalid-data-state",
|
|
48
|
+
message: "dataState: \"hourly_all\" requires grouping by hour dimension"
|
|
49
|
+
};
|
|
50
|
+
},
|
|
51
|
+
byPropertyUnsupportedSearchType() {
|
|
52
|
+
return {
|
|
53
|
+
kind: "invalid-aggregation-type",
|
|
54
|
+
message: "aggregationType: \"byProperty\" is not supported for type \"discover\" or \"googleNews\""
|
|
55
|
+
};
|
|
56
|
+
},
|
|
57
|
+
byPropertyNotAllowedWithPage() {
|
|
58
|
+
return {
|
|
59
|
+
kind: "invalid-aggregation-type",
|
|
60
|
+
message: "aggregationType: \"byProperty\" is not allowed when grouping or filtering by page"
|
|
61
|
+
};
|
|
62
|
+
},
|
|
63
|
+
byNewsShowcaseRequiresSearchType() {
|
|
64
|
+
return {
|
|
65
|
+
kind: "invalid-aggregation-type",
|
|
66
|
+
message: "aggregationType: \"byNewsShowcasePanel\" requires type \"discover\" or \"googleNews\""
|
|
67
|
+
};
|
|
68
|
+
},
|
|
69
|
+
byNewsShowcaseNotAllowedWithPage() {
|
|
70
|
+
return {
|
|
71
|
+
kind: "invalid-aggregation-type",
|
|
72
|
+
message: "aggregationType: \"byNewsShowcasePanel\" is not allowed when grouping or filtering by page"
|
|
73
|
+
};
|
|
74
|
+
},
|
|
75
|
+
byNewsShowcaseRequiresShowcaseFilter() {
|
|
76
|
+
return {
|
|
77
|
+
kind: "invalid-aggregation-type",
|
|
78
|
+
message: "aggregationType: \"byNewsShowcasePanel\" requires a searchAppearance equals \"NEWS_SHOWCASE\" filter and no other searchAppearance filter"
|
|
79
|
+
};
|
|
80
|
+
},
|
|
81
|
+
invalidBuilderState(cause) {
|
|
82
|
+
return {
|
|
83
|
+
kind: "invalid-builder-state",
|
|
84
|
+
cause,
|
|
85
|
+
message: "Invalid state"
|
|
86
|
+
};
|
|
87
|
+
},
|
|
88
|
+
unsupportedCapability(capability, context) {
|
|
89
|
+
return {
|
|
90
|
+
kind: "unsupported-capability",
|
|
91
|
+
capability,
|
|
92
|
+
context,
|
|
93
|
+
message: `${context} requires ${capability} capability`
|
|
94
|
+
};
|
|
95
|
+
},
|
|
96
|
+
unresolvableDataset(dimensions, filterDims = []) {
|
|
97
|
+
const grouped = dimensions.filter((d) => !TIME_AXIS_DIMENSIONS$1.has(d));
|
|
98
|
+
const filtered = filterDims.filter((d) => !TIME_AXIS_DIMENSIONS$1.has(d));
|
|
99
|
+
return {
|
|
100
|
+
kind: "unresolvable-dataset",
|
|
101
|
+
dimensions,
|
|
102
|
+
filterDims,
|
|
103
|
+
message: `Cannot resolve a [${grouped.join(", ")}] breakdown filtered by [${filtered.join(", ")}] from stored data: these dimensions live in separate per-dimension tables. Only the live GSC API computes cross-dimension aggregates.`
|
|
104
|
+
};
|
|
105
|
+
}
|
|
106
|
+
};
|
|
107
|
+
var UnsupportedLogicalCapabilityError = class extends Error {
|
|
108
|
+
queryError;
|
|
109
|
+
constructor(capability, context) {
|
|
110
|
+
const error = queryErrors.unsupportedCapability(capability, context);
|
|
111
|
+
super(error.message);
|
|
112
|
+
this.name = "UnsupportedLogicalCapabilityError";
|
|
113
|
+
this.queryError = error;
|
|
114
|
+
}
|
|
115
|
+
};
|
|
116
|
+
var UnresolvableDatasetError = class extends Error {
|
|
117
|
+
queryError;
|
|
118
|
+
constructor(dimensions, filterDims = []) {
|
|
119
|
+
const error = queryErrors.unresolvableDataset(dimensions, filterDims);
|
|
120
|
+
super(error.message);
|
|
121
|
+
this.name = "UnresolvableDatasetError";
|
|
122
|
+
this.queryError = error;
|
|
123
|
+
}
|
|
124
|
+
};
|
|
125
|
+
function queryErrorToException(error) {
|
|
126
|
+
switch (error.kind) {
|
|
127
|
+
case "unsupported-capability": return new UnsupportedLogicalCapabilityError(error.capability, error.context);
|
|
128
|
+
case "unresolvable-dataset": return new UnresolvableDatasetError(error.dimensions, error.filterDims);
|
|
129
|
+
default: {
|
|
130
|
+
const exception = new Error(error.message);
|
|
131
|
+
if ("cause" in error && error.cause !== void 0) exception.cause = error.cause;
|
|
132
|
+
exception.queryError = error;
|
|
133
|
+
return exception;
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
}
|
|
1
137
|
const DATE_OPERATORS = [
|
|
2
138
|
"gte",
|
|
3
139
|
"gt",
|
|
@@ -1665,12 +1801,6 @@ function extractSpecialOperatorFilters(input) {
|
|
|
1665
1801
|
const nested = filter._nestedGroups?.flatMap((g) => extractSpecialOperatorFilters(g)) ?? [];
|
|
1666
1802
|
return [...special, ...nested];
|
|
1667
1803
|
}
|
|
1668
|
-
var UnsupportedLogicalCapabilityError = class extends Error {
|
|
1669
|
-
constructor(capability, context) {
|
|
1670
|
-
super(`${context} requires ${capability} capability`);
|
|
1671
|
-
this.name = "UnsupportedLogicalCapabilityError";
|
|
1672
|
-
}
|
|
1673
|
-
};
|
|
1674
1804
|
function collectInternalFilters(filter) {
|
|
1675
1805
|
if (!filter || !("_filters" in filter)) return [];
|
|
1676
1806
|
const flat = filter._filters;
|
|
@@ -1713,21 +1843,10 @@ function isDatasetResolvable(dimensions, filterDims = []) {
|
|
|
1713
1843
|
if (needed.size === 0) return true;
|
|
1714
1844
|
return RESOLVABLE_DIMENSION_FAMILIES.some((family) => [...needed].every((d) => family.has(d)));
|
|
1715
1845
|
}
|
|
1716
|
-
var UnresolvableDatasetError = class extends Error {
|
|
1717
|
-
constructor(dimensions, filterDims = []) {
|
|
1718
|
-
const grouped = dimensions.filter((d) => !TIME_AXIS_DIMENSIONS.has(d));
|
|
1719
|
-
const filtered = filterDims.filter((d) => !TIME_AXIS_DIMENSIONS.has(d));
|
|
1720
|
-
super(`Cannot resolve a [${grouped.join(", ")}] breakdown filtered by [${filtered.join(", ")}] from stored data: these dimensions live in separate per-dimension tables. Only the live GSC API computes cross-dimension aggregates.`);
|
|
1721
|
-
this.name = "UnresolvableDatasetError";
|
|
1722
|
-
}
|
|
1723
|
-
};
|
|
1724
1846
|
function isStateResolvable(state) {
|
|
1725
1847
|
const filterDims = collectInternalFilters(normalizeFilter(state.filter)).filter((f) => !isMetric(f.dimension)).filter((f) => !(f.dimension === "date" && isDateOperator(f.operator))).filter((f) => !(f.operator === "topLevel" || f.operator.startsWith("metric"))).filter((f) => !isQueryParam(f.dimension)).map((f) => f.dimension);
|
|
1726
1848
|
return isDatasetResolvable(state.dimensions, filterDims);
|
|
1727
1849
|
}
|
|
1728
|
-
function requireCapability(capabilities, capability, enabled, context) {
|
|
1729
|
-
if (enabled && !capabilities?.[capability]) throw new UnsupportedLogicalCapabilityError(capability, context);
|
|
1730
|
-
}
|
|
1731
1850
|
function isDimensionLeaf(filter) {
|
|
1732
1851
|
if (isMetric(filter.dimension)) return false;
|
|
1733
1852
|
if (filter.dimension === "date" && isDateOperator(filter.operator)) return false;
|
|
@@ -1743,20 +1862,19 @@ function toLogicalDimensionFilter(filter) {
|
|
|
1743
1862
|
expression2: filter.expression2
|
|
1744
1863
|
};
|
|
1745
1864
|
}
|
|
1746
|
-
function buildDimensionFilterTree(filter
|
|
1865
|
+
function buildDimensionFilterTree(filter) {
|
|
1747
1866
|
if (!filter || !("_filters" in filter)) return void 0;
|
|
1748
1867
|
const groupType = filter._groupType ?? "and";
|
|
1749
1868
|
const children = [];
|
|
1750
1869
|
for (const f of filter._filters) {
|
|
1751
1870
|
if (!isDimensionLeaf(f)) continue;
|
|
1752
|
-
requireCapability(capabilities, "regex", isRegexOperator(f.operator), "logical plan");
|
|
1753
1871
|
children.push({
|
|
1754
1872
|
kind: "leaf",
|
|
1755
1873
|
filter: toLogicalDimensionFilter(f)
|
|
1756
1874
|
});
|
|
1757
1875
|
}
|
|
1758
1876
|
for (const g of filter._nestedGroups ?? []) {
|
|
1759
|
-
const sub = buildDimensionFilterTree(g
|
|
1877
|
+
const sub = buildDimensionFilterTree(g);
|
|
1760
1878
|
if (sub) children.push(sub);
|
|
1761
1879
|
}
|
|
1762
1880
|
if (children.length === 0) return void 0;
|
|
@@ -1767,11 +1885,12 @@ function buildDimensionFilterTree(filter, capabilities) {
|
|
|
1767
1885
|
children
|
|
1768
1886
|
};
|
|
1769
1887
|
}
|
|
1770
|
-
function
|
|
1888
|
+
function buildLogicalPlanResult(state, capabilities = {}) {
|
|
1771
1889
|
const normalizedFilter = normalizeFilter(state.filter);
|
|
1772
1890
|
const { startDate, endDate } = extractDateRange(normalizedFilter);
|
|
1773
|
-
if (!startDate || !endDate)
|
|
1891
|
+
if (!startDate || !endDate) return err(queryErrors.missingDateRange());
|
|
1774
1892
|
const allFilters = collectInternalFilters(normalizedFilter);
|
|
1893
|
+
if (!capabilities.regex && allFilters.some((f) => isDimensionLeaf(f) && isRegexOperator(f.operator))) return err(queryErrors.unsupportedCapability("regex", "logical plan"));
|
|
1775
1894
|
const metricFilters = extractMetricFilters(normalizedFilter);
|
|
1776
1895
|
const specialFilters = extractSpecialOperatorFilters(normalizedFilter);
|
|
1777
1896
|
const prefilters = extractMetricFilters(normalizeFilter(state.prefilter));
|
|
@@ -1785,19 +1904,17 @@ function buildLogicalPlan(state, capabilities = {}) {
|
|
|
1785
1904
|
queryParams[filter.dimension] = filter.expression;
|
|
1786
1905
|
continue;
|
|
1787
1906
|
}
|
|
1788
|
-
const operator = filter.operator;
|
|
1789
|
-
requireCapability(capabilities, "regex", isRegexOperator(operator), "logical plan");
|
|
1790
1907
|
dimensionFilters.push({
|
|
1791
1908
|
dimension: filter.dimension,
|
|
1792
|
-
operator,
|
|
1909
|
+
operator: filter.operator,
|
|
1793
1910
|
expression: filter.expression,
|
|
1794
1911
|
expression2: filter.expression2
|
|
1795
1912
|
});
|
|
1796
1913
|
}
|
|
1797
|
-
const dimensionFilterTree = buildDimensionFilterTree(normalizedFilter
|
|
1914
|
+
const dimensionFilterTree = buildDimensionFilterTree(normalizedFilter);
|
|
1798
1915
|
const filterDims = dimensionFilters.map((filter) => filter.dimension);
|
|
1799
|
-
if (!isDatasetResolvable(state.dimensions, filterDims))
|
|
1800
|
-
return {
|
|
1916
|
+
if (!isDatasetResolvable(state.dimensions, filterDims)) return err(queryErrors.unresolvableDataset(state.dimensions, filterDims));
|
|
1917
|
+
return ok({
|
|
1801
1918
|
dataset: inferDataset(state.dimensions, filterDims),
|
|
1802
1919
|
dimensions: [...state.dimensions],
|
|
1803
1920
|
groupByDimensions: state.dimensions.filter((d) => d !== "date"),
|
|
@@ -1831,17 +1948,25 @@ function buildLogicalPlan(state, capabilities = {}) {
|
|
|
1831
1948
|
orderBy: state.orderBy,
|
|
1832
1949
|
rowLimit: state.rowLimit,
|
|
1833
1950
|
startRow: state.startRow
|
|
1834
|
-
};
|
|
1951
|
+
});
|
|
1835
1952
|
}
|
|
1836
|
-
function
|
|
1837
|
-
|
|
1838
|
-
|
|
1839
|
-
|
|
1840
|
-
|
|
1841
|
-
|
|
1842
|
-
|
|
1843
|
-
|
|
1953
|
+
function buildLogicalPlan(state, capabilities = {}) {
|
|
1954
|
+
return unwrapResult(buildLogicalPlanResult(state, capabilities), queryErrorToException);
|
|
1955
|
+
}
|
|
1956
|
+
function buildLogicalComparisonPlanResult(current, previous, capabilities = {}, comparisonFilter) {
|
|
1957
|
+
if (!capabilities.comparisonJoin) return err(queryErrors.unsupportedCapability("comparisonJoin", "logical comparison plan"));
|
|
1958
|
+
const currentResult = buildLogicalPlanResult(current, capabilities);
|
|
1959
|
+
if (!currentResult.ok) return currentResult;
|
|
1960
|
+
const previousResult = buildLogicalPlanResult(previous, capabilities);
|
|
1961
|
+
if (!previousResult.ok) return previousResult;
|
|
1962
|
+
if (currentResult.value.dataset !== previousResult.value.dataset && !capabilities.multiDataset) return err(queryErrors.unsupportedCapability("multiDataset", "logical comparison plan"));
|
|
1963
|
+
return ok({
|
|
1964
|
+
current: currentResult.value,
|
|
1965
|
+
previous: previousResult.value,
|
|
1844
1966
|
comparisonFilter
|
|
1845
|
-
};
|
|
1967
|
+
});
|
|
1968
|
+
}
|
|
1969
|
+
function buildLogicalComparisonPlan(current, previous, capabilities = {}, comparisonFilter) {
|
|
1970
|
+
return unwrapResult(buildLogicalComparisonPlanResult(current, previous, capabilities, comparisonFilter), queryErrorToException);
|
|
1846
1971
|
}
|
|
1847
|
-
export { UnresolvableDatasetError, UnsupportedLogicalCapabilityError, buildLogicalComparisonPlan, buildLogicalPlan, inferDataset, isDatasetResolvable, isStateResolvable };
|
|
1972
|
+
export { UnresolvableDatasetError, UnsupportedLogicalCapabilityError, buildLogicalComparisonPlan, buildLogicalComparisonPlanResult, buildLogicalPlan, buildLogicalPlanResult, inferDataset, isDatasetResolvable, isStateResolvable };
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "gscdump",
|
|
3
3
|
"type": "module",
|
|
4
|
-
"version": "0.
|
|
4
|
+
"version": "0.26.0",
|
|
5
5
|
"description": "Google Search Console API wrapper with typed query builder, streaming pagination, and SEO analysis functions",
|
|
6
6
|
"author": {
|
|
7
7
|
"name": "Harlan Wilton",
|
|
@@ -47,6 +47,11 @@
|
|
|
47
47
|
"import": "./dist/query/plan.mjs",
|
|
48
48
|
"default": "./dist/query/plan.mjs"
|
|
49
49
|
},
|
|
50
|
+
"./result": {
|
|
51
|
+
"types": "./dist/core/result.d.mts",
|
|
52
|
+
"import": "./dist/core/result.mjs",
|
|
53
|
+
"default": "./dist/core/result.mjs"
|
|
54
|
+
},
|
|
50
55
|
"./contracts": {
|
|
51
56
|
"types": "./dist/contracts.d.mts",
|
|
52
57
|
"import": "./dist/contracts.mjs",
|
|
@@ -78,7 +83,7 @@
|
|
|
78
83
|
"defu": "^6.1.7",
|
|
79
84
|
"ofetch": "^1.5.1",
|
|
80
85
|
"ufo": "^1.6.4",
|
|
81
|
-
"@gscdump/contracts": "0.
|
|
86
|
+
"@gscdump/contracts": "0.26.0"
|
|
82
87
|
},
|
|
83
88
|
"devDependencies": {
|
|
84
89
|
"@googleapis/indexing": "^6.0.1",
|