gscdump 0.19.1 → 0.19.3
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/query/index.mjs +25 -0
- package/dist/query/plan.d.mts +25 -1
- package/dist/query/plan.mjs +30 -1
- package/package.json +2 -2
package/dist/query/index.mjs
CHANGED
|
@@ -2042,6 +2042,30 @@ function inferDataset(dimensions, filterDims = []) {
|
|
|
2042
2042
|
if (has("device")) return "devices";
|
|
2043
2043
|
return "devices";
|
|
2044
2044
|
}
|
|
2045
|
+
const RESOLVABLE_DIMENSION_FAMILIES = [
|
|
2046
|
+
new Set([
|
|
2047
|
+
"page",
|
|
2048
|
+
"query",
|
|
2049
|
+
"queryCanonical"
|
|
2050
|
+
]),
|
|
2051
|
+
new Set(["country"]),
|
|
2052
|
+
new Set(["device"]),
|
|
2053
|
+
new Set(["searchAppearance"])
|
|
2054
|
+
];
|
|
2055
|
+
const TIME_AXIS_DIMENSIONS = new Set(["date", "hour"]);
|
|
2056
|
+
function isDatasetResolvable(dimensions, filterDims = []) {
|
|
2057
|
+
const needed = new Set([...dimensions, ...filterDims].filter((d) => !TIME_AXIS_DIMENSIONS.has(d)));
|
|
2058
|
+
if (needed.size === 0) return true;
|
|
2059
|
+
return RESOLVABLE_DIMENSION_FAMILIES.some((family) => [...needed].every((d) => family.has(d)));
|
|
2060
|
+
}
|
|
2061
|
+
var UnresolvableDatasetError = class extends Error {
|
|
2062
|
+
constructor(dimensions, filterDims = []) {
|
|
2063
|
+
const grouped = dimensions.filter((d) => !TIME_AXIS_DIMENSIONS.has(d));
|
|
2064
|
+
const filtered = filterDims.filter((d) => !TIME_AXIS_DIMENSIONS.has(d));
|
|
2065
|
+
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.`);
|
|
2066
|
+
this.name = "UnresolvableDatasetError";
|
|
2067
|
+
}
|
|
2068
|
+
};
|
|
2045
2069
|
function requireCapability(capabilities, capability, enabled, context) {
|
|
2046
2070
|
if (enabled && !capabilities?.[capability]) throw new UnsupportedLogicalCapabilityError(capability, context);
|
|
2047
2071
|
}
|
|
@@ -2113,6 +2137,7 @@ function buildLogicalPlan(state, capabilities = {}) {
|
|
|
2113
2137
|
}
|
|
2114
2138
|
const dimensionFilterTree = buildDimensionFilterTree(normalizedFilter, capabilities);
|
|
2115
2139
|
const filterDims = dimensionFilters.map((filter) => filter.dimension);
|
|
2140
|
+
if (!isDatasetResolvable(state.dimensions, filterDims)) throw new UnresolvableDatasetError(state.dimensions, filterDims);
|
|
2116
2141
|
return {
|
|
2117
2142
|
dataset: inferDataset(state.dimensions, filterDims),
|
|
2118
2143
|
dimensions: [...state.dimensions],
|
package/dist/query/plan.d.mts
CHANGED
|
@@ -136,6 +136,30 @@ interface LogicalComparisonPlan {
|
|
|
136
136
|
declare class UnsupportedLogicalCapabilityError extends Error {
|
|
137
137
|
constructor(capability: keyof PlannerCapabilities, context: string);
|
|
138
138
|
}
|
|
139
|
+
declare function inferDataset(dimensions: readonly Dimension[], filterDims?: readonly Dimension[]): LogicalDataset;
|
|
140
|
+
/**
|
|
141
|
+
* True when every grouped + filtered dimension fits inside one stored dataset,
|
|
142
|
+
* i.e. the query is answerable from stored Parquet/D1 tables without a live
|
|
143
|
+
* GSC call. `inferDataset` always returns *some* dataset; this predicate is
|
|
144
|
+
* how callers tell a genuine match from one that will fail at column-resolve
|
|
145
|
+
* time.
|
|
146
|
+
*/
|
|
147
|
+
declare function isDatasetResolvable(dimensions: readonly Dimension[], filterDims?: readonly Dimension[]): boolean;
|
|
148
|
+
/**
|
|
149
|
+
* Thrown when a query's grouped + filtered dimensions span more than one
|
|
150
|
+
* stored dataset. Replaces the resolver's raw "unknown column" error so hosts
|
|
151
|
+
* can map it to a 4xx instead of leaking an opaque 500.
|
|
152
|
+
*/
|
|
153
|
+
declare class UnresolvableDatasetError extends Error {
|
|
154
|
+
constructor(dimensions: readonly Dimension[], filterDims?: readonly Dimension[]);
|
|
155
|
+
}
|
|
156
|
+
/**
|
|
157
|
+
* `BuilderState`-level convenience for {@link isDatasetResolvable}: extracts
|
|
158
|
+
* the state's dimension filters (the same way `buildLogicalPlan` does) and
|
|
159
|
+
* checks them against the grouped dimensions. Lets routing code (e.g. the
|
|
160
|
+
* composite source) detect a cross-dimension query without rebuilding a plan.
|
|
161
|
+
*/
|
|
162
|
+
declare function isStateResolvable(state: BuilderState): boolean;
|
|
139
163
|
declare function buildLogicalPlan(state: BuilderState, capabilities?: PlannerCapabilities): LogicalQueryPlan;
|
|
140
164
|
declare function buildLogicalComparisonPlan(current: BuilderState, previous: BuilderState, capabilities?: PlannerCapabilities, comparisonFilter?: ComparisonFilter): LogicalComparisonPlan;
|
|
141
|
-
export { ComparisonFilter, LogicalComparisonPlan, LogicalDataset, LogicalDimensionFilter, LogicalFilterGroup, LogicalFilterLeaf, LogicalFilterNode, LogicalMetricFilter, LogicalQueryPlan, PlannerCapabilities, type TableName, UnsupportedLogicalCapabilityError, buildLogicalComparisonPlan, buildLogicalPlan };
|
|
165
|
+
export { ComparisonFilter, LogicalComparisonPlan, LogicalDataset, LogicalDimensionFilter, LogicalFilterGroup, LogicalFilterLeaf, LogicalFilterNode, LogicalMetricFilter, LogicalQueryPlan, PlannerCapabilities, type TableName, UnresolvableDatasetError, UnsupportedLogicalCapabilityError, buildLogicalComparisonPlan, buildLogicalPlan, inferDataset, isDatasetResolvable, isStateResolvable };
|
package/dist/query/plan.mjs
CHANGED
|
@@ -1688,6 +1688,34 @@ function inferDataset(dimensions, filterDims = []) {
|
|
|
1688
1688
|
if (has("device")) return "devices";
|
|
1689
1689
|
return "devices";
|
|
1690
1690
|
}
|
|
1691
|
+
const RESOLVABLE_DIMENSION_FAMILIES = [
|
|
1692
|
+
new Set([
|
|
1693
|
+
"page",
|
|
1694
|
+
"query",
|
|
1695
|
+
"queryCanonical"
|
|
1696
|
+
]),
|
|
1697
|
+
new Set(["country"]),
|
|
1698
|
+
new Set(["device"]),
|
|
1699
|
+
new Set(["searchAppearance"])
|
|
1700
|
+
];
|
|
1701
|
+
const TIME_AXIS_DIMENSIONS = new Set(["date", "hour"]);
|
|
1702
|
+
function isDatasetResolvable(dimensions, filterDims = []) {
|
|
1703
|
+
const needed = new Set([...dimensions, ...filterDims].filter((d) => !TIME_AXIS_DIMENSIONS.has(d)));
|
|
1704
|
+
if (needed.size === 0) return true;
|
|
1705
|
+
return RESOLVABLE_DIMENSION_FAMILIES.some((family) => [...needed].every((d) => family.has(d)));
|
|
1706
|
+
}
|
|
1707
|
+
var UnresolvableDatasetError = class extends Error {
|
|
1708
|
+
constructor(dimensions, filterDims = []) {
|
|
1709
|
+
const grouped = dimensions.filter((d) => !TIME_AXIS_DIMENSIONS.has(d));
|
|
1710
|
+
const filtered = filterDims.filter((d) => !TIME_AXIS_DIMENSIONS.has(d));
|
|
1711
|
+
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.`);
|
|
1712
|
+
this.name = "UnresolvableDatasetError";
|
|
1713
|
+
}
|
|
1714
|
+
};
|
|
1715
|
+
function isStateResolvable(state) {
|
|
1716
|
+
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);
|
|
1717
|
+
return isDatasetResolvable(state.dimensions, filterDims);
|
|
1718
|
+
}
|
|
1691
1719
|
function requireCapability(capabilities, capability, enabled, context) {
|
|
1692
1720
|
if (enabled && !capabilities?.[capability]) throw new UnsupportedLogicalCapabilityError(capability, context);
|
|
1693
1721
|
}
|
|
@@ -1759,6 +1787,7 @@ function buildLogicalPlan(state, capabilities = {}) {
|
|
|
1759
1787
|
}
|
|
1760
1788
|
const dimensionFilterTree = buildDimensionFilterTree(normalizedFilter, capabilities);
|
|
1761
1789
|
const filterDims = dimensionFilters.map((filter) => filter.dimension);
|
|
1790
|
+
if (!isDatasetResolvable(state.dimensions, filterDims)) throw new UnresolvableDatasetError(state.dimensions, filterDims);
|
|
1762
1791
|
return {
|
|
1763
1792
|
dataset: inferDataset(state.dimensions, filterDims),
|
|
1764
1793
|
dimensions: [...state.dimensions],
|
|
@@ -1806,4 +1835,4 @@ function buildLogicalComparisonPlan(current, previous, capabilities = {}, compar
|
|
|
1806
1835
|
comparisonFilter
|
|
1807
1836
|
};
|
|
1808
1837
|
}
|
|
1809
|
-
export { UnsupportedLogicalCapabilityError, buildLogicalComparisonPlan, buildLogicalPlan };
|
|
1838
|
+
export { UnresolvableDatasetError, UnsupportedLogicalCapabilityError, buildLogicalComparisonPlan, buildLogicalPlan, inferDataset, isDatasetResolvable, isStateResolvable };
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "gscdump",
|
|
3
3
|
"type": "module",
|
|
4
|
-
"version": "0.19.
|
|
4
|
+
"version": "0.19.3",
|
|
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",
|
|
@@ -108,7 +108,7 @@
|
|
|
108
108
|
"defu": "^6.1.7",
|
|
109
109
|
"ofetch": "^1.5.1",
|
|
110
110
|
"ufo": "^1.6.4",
|
|
111
|
-
"@gscdump/contracts": "0.19.
|
|
111
|
+
"@gscdump/contracts": "0.19.3"
|
|
112
112
|
},
|
|
113
113
|
"devDependencies": {
|
|
114
114
|
"@googleapis/indexing": "^6.0.1",
|