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.
@@ -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, capabilities) {
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, capabilities);
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 buildLogicalPlan(state, capabilities = {}) {
1888
+ function buildLogicalPlanResult(state, capabilities = {}) {
1771
1889
  const normalizedFilter = normalizeFilter(state.filter);
1772
1890
  const { startDate, endDate } = extractDateRange(normalizedFilter);
1773
- if (!startDate || !endDate) throw new Error("logical plan requires date range (use between(date, ...) or gte/lte)");
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, capabilities);
1914
+ const dimensionFilterTree = buildDimensionFilterTree(normalizedFilter);
1798
1915
  const filterDims = dimensionFilters.map((filter) => filter.dimension);
1799
- if (!isDatasetResolvable(state.dimensions, filterDims)) throw new UnresolvableDatasetError(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 buildLogicalComparisonPlan(current, previous, capabilities = {}, comparisonFilter) {
1837
- requireCapability(capabilities, "comparisonJoin", true, "logical comparison plan");
1838
- const currentPlan = buildLogicalPlan(current, capabilities);
1839
- const previousPlan = buildLogicalPlan(previous, capabilities);
1840
- requireCapability(capabilities, "multiDataset", currentPlan.dataset !== previousPlan.dataset, "logical comparison plan");
1841
- return {
1842
- current: currentPlan,
1843
- previous: previousPlan,
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.25.14",
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.25.14"
86
+ "@gscdump/contracts": "0.26.0"
82
87
  },
83
88
  "devDependencies": {
84
89
  "@googleapis/indexing": "^6.0.1",