gscdump 3.4.4 → 3.6.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/README.md +36 -7
- package/dist/api/batch.d.mts +2 -3
- package/dist/api/batch.mjs +2 -0
- package/dist/api/indexing.d.mts +7 -8
- package/dist/api/indexing.mjs +1 -1
- package/dist/api/inspection.d.mts +21 -22
- package/dist/api/oauth.d.mts +8 -9
- package/dist/api/oauth.mjs +9 -1
- package/dist/api/sites.d.mts +9 -10
- package/dist/api/verification.d.mts +8 -9
- package/dist/bing/client.d.mts +3 -4
- package/dist/bing/client.mjs +10 -4
- package/dist/bing/normalize.d.mts +9 -10
- package/dist/bing/types.d.mts +42 -38
- package/dist/contracts.d.mts +12 -12
- package/dist/core/canonical.d.mts +3 -4
- package/dist/core/client.d.mts +16 -17
- package/dist/core/client.mjs +16 -13
- package/dist/core/errors.d.mts +9 -10
- package/dist/core/errors.mjs +2 -1
- package/dist/core/gsc-dates.d.mts +22 -23
- package/dist/core/indexing-issues.d.mts +10 -11
- package/dist/core/property.d.mts +9 -10
- package/dist/core/quota.d.mts +1 -2
- package/dist/core/result.d.mts +8 -9
- package/dist/core/scope-values.d.mts +5 -6
- package/dist/core/scopes.d.mts +3 -4
- package/dist/core/site-url.d.mts +4 -5
- package/dist/core/types.d.mts +29 -30
- package/dist/core/window.d.mts +5 -6
- package/dist/normalize.d.mts +1 -2
- package/dist/query/builder.d.mts +4 -5
- package/dist/query/columns.d.mts +13 -14
- package/dist/query/constants.d.mts +5 -5
- package/dist/query/errors.d.mts +7 -8
- package/dist/query/errors.mjs +1 -1
- package/dist/query/index.d.mts +3 -3
- package/dist/query/operators.d.mts +22 -23
- package/dist/query/plan.d.mts +18 -18
- package/dist/query/plan.mjs +7 -4
- package/dist/query/resolver.d.mts +10 -19
- package/dist/query/resolver.mjs +125 -61
- package/dist/query/types.d.mts +22 -23
- package/dist/query/utils/dayjs.d.mts +2 -3
- package/dist/sitemap-identity.d.mts +8 -9
- package/dist/tenant.d.mts +3 -4
- package/dist/url.d.mts +1 -2
- package/package.json +2 -2
package/dist/query/plan.d.mts
CHANGED
|
@@ -2,37 +2,37 @@ import { Result } from "../core/result.mjs";
|
|
|
2
2
|
import { TableName } from "../contracts.mjs";
|
|
3
3
|
import { BuilderState, Dimension, FilterOperator, Metric, MetricOperator, QueryParamName } from "./types.mjs";
|
|
4
4
|
import { QueryError, QueryErrorKind, UnresolvableDatasetError, UnsupportedLogicalCapabilityError } from "./errors.mjs";
|
|
5
|
-
type LogicalDataset = TableName;
|
|
6
|
-
type ComparisonFilter = 'new' | 'lost' | 'improving' | 'declining';
|
|
7
|
-
interface PlannerCapabilities {
|
|
5
|
+
export type LogicalDataset = TableName;
|
|
6
|
+
export type ComparisonFilter = 'new' | 'lost' | 'improving' | 'declining';
|
|
7
|
+
export interface PlannerCapabilities {
|
|
8
8
|
regex?: boolean;
|
|
9
9
|
multiDataset?: boolean;
|
|
10
10
|
comparisonJoin?: boolean;
|
|
11
11
|
windowTotals?: boolean;
|
|
12
12
|
}
|
|
13
|
-
interface LogicalDimensionFilter {
|
|
13
|
+
export interface LogicalDimensionFilter {
|
|
14
14
|
dimension: Dimension;
|
|
15
15
|
operator: FilterOperator;
|
|
16
16
|
expression: string;
|
|
17
17
|
expression2?: string;
|
|
18
18
|
}
|
|
19
|
-
interface LogicalMetricFilter {
|
|
19
|
+
export interface LogicalMetricFilter {
|
|
20
20
|
metric: Metric;
|
|
21
21
|
operator: MetricOperator;
|
|
22
22
|
expression: number;
|
|
23
23
|
expression2?: number;
|
|
24
24
|
}
|
|
25
|
-
interface LogicalFilterLeaf {
|
|
25
|
+
export interface LogicalFilterLeaf {
|
|
26
26
|
kind: 'leaf';
|
|
27
27
|
filter: LogicalDimensionFilter;
|
|
28
28
|
}
|
|
29
|
-
interface LogicalFilterGroup {
|
|
29
|
+
export interface LogicalFilterGroup {
|
|
30
30
|
kind: 'group';
|
|
31
31
|
groupType: 'and' | 'or';
|
|
32
32
|
children: LogicalFilterNode[];
|
|
33
33
|
}
|
|
34
|
-
type LogicalFilterNode = LogicalFilterLeaf | LogicalFilterGroup;
|
|
35
|
-
interface LogicalQueryPlan {
|
|
34
|
+
export type LogicalFilterNode = LogicalFilterLeaf | LogicalFilterGroup;
|
|
35
|
+
export interface LogicalQueryPlan {
|
|
36
36
|
dataset: LogicalDataset;
|
|
37
37
|
dimensions: Dimension[];
|
|
38
38
|
groupByDimensions: Dimension[];
|
|
@@ -54,12 +54,12 @@ interface LogicalQueryPlan {
|
|
|
54
54
|
rowLimit?: number;
|
|
55
55
|
startRow?: number;
|
|
56
56
|
}
|
|
57
|
-
interface LogicalComparisonPlan {
|
|
57
|
+
export interface LogicalComparisonPlan {
|
|
58
58
|
current: LogicalQueryPlan;
|
|
59
59
|
previous: LogicalQueryPlan;
|
|
60
60
|
comparisonFilter?: ComparisonFilter;
|
|
61
61
|
}
|
|
62
|
-
declare function inferDataset(dimensions: readonly Dimension[], filterDims?: readonly Dimension[]): LogicalDataset;
|
|
62
|
+
export declare function inferDataset(dimensions: readonly Dimension[], filterDims?: readonly Dimension[]): LogicalDataset;
|
|
63
63
|
/**
|
|
64
64
|
* True when every grouped + filtered dimension fits inside one stored dataset,
|
|
65
65
|
* i.e. the query is answerable from stored Parquet/D1 tables without a live
|
|
@@ -67,27 +67,27 @@ declare function inferDataset(dimensions: readonly Dimension[], filterDims?: rea
|
|
|
67
67
|
* how callers tell a genuine match from one that will fail at column-resolve
|
|
68
68
|
* time.
|
|
69
69
|
*/
|
|
70
|
-
declare function isDatasetResolvable(dimensions: readonly Dimension[], filterDims?: readonly Dimension[]): boolean;
|
|
70
|
+
export declare function isDatasetResolvable(dimensions: readonly Dimension[], filterDims?: readonly Dimension[]): boolean;
|
|
71
71
|
/**
|
|
72
72
|
* `BuilderState`-level convenience for {@link isDatasetResolvable}: extracts
|
|
73
73
|
* the state's dimension filters (the same way `buildLogicalPlan` does) and
|
|
74
74
|
* checks them against the grouped dimensions. Lets routing code (e.g. the
|
|
75
75
|
* composite source) detect a cross-dimension query without rebuilding a plan.
|
|
76
76
|
*/
|
|
77
|
-
declare function isStateResolvable(state: BuilderState): boolean;
|
|
77
|
+
export declare function isStateResolvable(state: BuilderState): boolean;
|
|
78
78
|
/**
|
|
79
79
|
* Errors-as-values core: builds the logical plan or returns a typed `QueryError`
|
|
80
80
|
* for every modelled failure (missing date range, a regex filter on an engine
|
|
81
81
|
* without regex pushdown, a cross-dimension query with no stored home).
|
|
82
82
|
* `buildLogicalPlan` is the throwing wrapper for call sites that prefer exceptions.
|
|
83
83
|
*/
|
|
84
|
-
declare function buildLogicalPlanResult(
|
|
85
|
-
declare function buildLogicalPlan(state: BuilderState, capabilities?: PlannerCapabilities): LogicalQueryPlan;
|
|
84
|
+
export declare function buildLogicalPlanResult(input: BuilderState, capabilities?: PlannerCapabilities): Result<LogicalQueryPlan, QueryError>;
|
|
85
|
+
export declare function buildLogicalPlan(state: BuilderState, capabilities?: PlannerCapabilities): LogicalQueryPlan;
|
|
86
86
|
/**
|
|
87
87
|
* Errors-as-values core for the comparison plan: returns a typed `QueryError`
|
|
88
88
|
* when the engine lacks the comparison-join or multi-dataset capability the
|
|
89
89
|
* paired queries need, or when either side fails to plan.
|
|
90
90
|
*/
|
|
91
|
-
declare function buildLogicalComparisonPlanResult(current: BuilderState, previous: BuilderState, capabilities?: PlannerCapabilities, comparisonFilter?: ComparisonFilter): Result<LogicalComparisonPlan, QueryError>;
|
|
92
|
-
declare function buildLogicalComparisonPlan(current: BuilderState, previous: BuilderState, capabilities?: PlannerCapabilities, comparisonFilter?: ComparisonFilter): LogicalComparisonPlan;
|
|
93
|
-
export {
|
|
91
|
+
export declare function buildLogicalComparisonPlanResult(current: BuilderState, previous: BuilderState, capabilities?: PlannerCapabilities, comparisonFilter?: ComparisonFilter): Result<LogicalComparisonPlan, QueryError>;
|
|
92
|
+
export declare function buildLogicalComparisonPlan(current: BuilderState, previous: BuilderState, capabilities?: PlannerCapabilities, comparisonFilter?: ComparisonFilter): LogicalComparisonPlan;
|
|
93
|
+
export { type QueryError, type QueryErrorKind, type TableName, UnresolvableDatasetError, UnsupportedLogicalCapabilityError };
|
package/dist/query/plan.mjs
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { err, ok, unwrapResult } from "../core/result.mjs";
|
|
2
2
|
import { UnresolvableDatasetError, UnsupportedLogicalCapabilityError, queryErrorToException, queryErrors } from "./errors.mjs";
|
|
3
3
|
import { isDateOperator, isMetric, isQueryParam, isRegexOperator } from "./operator-meta.mjs";
|
|
4
|
-
import { extractDateRange, extractMetricFilters, extractSpecialOperatorFilters, normalizeFilter } from "./resolver.mjs";
|
|
4
|
+
import { extractDateRange, extractMetricFilters, extractSpecialOperatorFilters, normalizeBuilderStateResult, normalizeFilter } from "./resolver.mjs";
|
|
5
5
|
function collectInternalFilters(filter) {
|
|
6
6
|
if (!filter || !("_filters" in filter)) return [];
|
|
7
7
|
const flat = filter._filters;
|
|
@@ -86,15 +86,18 @@ function buildDimensionFilterTree(filter) {
|
|
|
86
86
|
children
|
|
87
87
|
};
|
|
88
88
|
}
|
|
89
|
-
function buildLogicalPlanResult(
|
|
90
|
-
const
|
|
89
|
+
function buildLogicalPlanResult(input, capabilities = {}) {
|
|
90
|
+
const parsed = normalizeBuilderStateResult(input);
|
|
91
|
+
if (!parsed.ok) return parsed;
|
|
92
|
+
const state = parsed.value;
|
|
93
|
+
const normalizedFilter = state.filter;
|
|
91
94
|
const { startDate, endDate } = extractDateRange(normalizedFilter);
|
|
92
95
|
if (!startDate || !endDate) return err(queryErrors.missingDateRange());
|
|
93
96
|
const allFilters = collectInternalFilters(normalizedFilter);
|
|
94
97
|
if (!capabilities.regex && allFilters.some((f) => isDimensionLeaf(f) && isRegexOperator(f.operator))) return err(queryErrors.unsupportedCapability("regex", "logical plan"));
|
|
95
98
|
const metricFilters = extractMetricFilters(normalizedFilter);
|
|
96
99
|
const specialFilters = extractSpecialOperatorFilters(normalizedFilter);
|
|
97
|
-
const normalizedPrefilter =
|
|
100
|
+
const normalizedPrefilter = state.prefilter;
|
|
98
101
|
const prefilters = extractMetricFilters(normalizedPrefilter);
|
|
99
102
|
const queryParams = {};
|
|
100
103
|
const dimensionFilters = [];
|
|
@@ -3,31 +3,23 @@ import { GscSearchAnalyticsRequest } from "../contracts.mjs";
|
|
|
3
3
|
import { SearchType } from "./constants.mjs";
|
|
4
4
|
import { BuilderState, Filter, FilterInput, InternalFilter } from "./types.mjs";
|
|
5
5
|
import { QueryError } from "./errors.mjs";
|
|
6
|
-
declare function normalizeFilter(input?: FilterInput): Filter<any> | undefined;
|
|
7
|
-
/**
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
* its string `dimension`/`operator`, instead of throwing. Also coerces
|
|
12
|
-
* alternative `orderBy` shapes into the canonical `{ column, dir }`.
|
|
13
|
-
* Receive-edge parse (parse, don't validate), so hosts can map a bad body to a
|
|
14
|
-
* 4xx and downstream consumers only ever see the canonical shape.
|
|
15
|
-
*/
|
|
16
|
-
declare function normalizeBuilderStateResult(state: unknown): Result<BuilderState, QueryError>;
|
|
17
|
-
declare function normalizeBuilderState(state: unknown): BuilderState;
|
|
18
|
-
declare function extractDateRange(input?: FilterInput): {
|
|
6
|
+
export declare function normalizeFilter(input?: FilterInput): Filter<any> | undefined;
|
|
7
|
+
/** Parse an untrusted query body. Expected input failures stay in the error channel. */
|
|
8
|
+
export declare function normalizeBuilderStateResult(state: unknown): Result<BuilderState, QueryError>;
|
|
9
|
+
export declare function normalizeBuilderState(state: unknown): BuilderState;
|
|
10
|
+
export declare function extractDateRange(input?: FilterInput): {
|
|
19
11
|
startDate?: string;
|
|
20
12
|
endDate?: string;
|
|
21
13
|
};
|
|
22
|
-
declare function extractMetricFilters(input?: FilterInput): InternalFilter[];
|
|
23
|
-
declare function extractSpecialOperatorFilters(input?: FilterInput): InternalFilter[];
|
|
14
|
+
export declare function extractMetricFilters(input?: FilterInput): InternalFilter[];
|
|
15
|
+
export declare function extractSpecialOperatorFilters(input?: FilterInput): InternalFilter[];
|
|
24
16
|
/**
|
|
25
17
|
* Pull `searchType` out of a BuilderState filter. Returns undefined for
|
|
26
18
|
* missing/invalid shapes — callers treat that as "no scope" (cross-type read).
|
|
27
19
|
* Validated against the canonical `SearchTypes` set so unknown strings
|
|
28
20
|
* don't reach the engine.
|
|
29
21
|
*/
|
|
30
|
-
declare function extractSearchType(state: BuilderState | undefined | null): SearchType | undefined;
|
|
22
|
+
export declare function extractSearchType(state: BuilderState | undefined | null): SearchType | undefined;
|
|
31
23
|
/**
|
|
32
24
|
* Errors-as-values core: turns a `BuilderState` into a GSC API request body or
|
|
33
25
|
* returns a typed `QueryError` for every modelled bad-query case (missing date
|
|
@@ -35,6 +27,5 @@ declare function extractSearchType(state: BuilderState | undefined | null): Sear
|
|
|
35
27
|
* aggregationType combination). `resolveToBody` is the throwing wrapper over this
|
|
36
28
|
* for `.toBody()` and the live-API client paths.
|
|
37
29
|
*/
|
|
38
|
-
declare function resolveToBodyResult(
|
|
39
|
-
declare function resolveToBody(state: BuilderState): GscSearchAnalyticsRequest;
|
|
40
|
-
export { extractDateRange, extractMetricFilters, extractSearchType, extractSpecialOperatorFilters, normalizeBuilderState, normalizeBuilderStateResult, normalizeFilter, resolveToBody, resolveToBodyResult };
|
|
30
|
+
export declare function resolveToBodyResult(input: BuilderState): Result<GscSearchAnalyticsRequest, QueryError>;
|
|
31
|
+
export declare function resolveToBody(state: BuilderState): GscSearchAnalyticsRequest;
|
package/dist/query/resolver.mjs
CHANGED
|
@@ -2,91 +2,152 @@ import { err, ok, unwrapResult } from "../core/result.mjs";
|
|
|
2
2
|
import { addDays } from "../core/gsc-dates.mjs";
|
|
3
3
|
import { SearchTypes } from "./constants.mjs";
|
|
4
4
|
import { queryErrorToException, queryErrors } from "./errors.mjs";
|
|
5
|
-
import { isDateOperator, isMetricOperator, isQueryParam, isSpecialOperator } from "./operator-meta.mjs";
|
|
5
|
+
import { isDateOperator, isMetric, isMetricOperator, isQueryParam, isSpecialOperator } from "./operator-meta.mjs";
|
|
6
|
+
const KNOWN_DIMENSIONS = /* @__PURE__ */ new Set([
|
|
7
|
+
"page",
|
|
8
|
+
"query",
|
|
9
|
+
"queryCanonical",
|
|
10
|
+
"country",
|
|
11
|
+
"device",
|
|
12
|
+
"date",
|
|
13
|
+
"hour",
|
|
14
|
+
"searchAppearance"
|
|
15
|
+
]);
|
|
16
|
+
const FILTER_OPERATORS = /* @__PURE__ */ new Set([
|
|
17
|
+
"equals",
|
|
18
|
+
"notEquals",
|
|
19
|
+
"contains",
|
|
20
|
+
"notContains",
|
|
21
|
+
"includingRegex",
|
|
22
|
+
"excludingRegex"
|
|
23
|
+
]);
|
|
6
24
|
const KNOWN_SEARCH_TYPES = new Set(Object.values(SearchTypes));
|
|
7
|
-
function
|
|
8
|
-
return
|
|
9
|
-
}
|
|
10
|
-
function convertWireLeaf(alt) {
|
|
11
|
-
if (!alt.column || !alt.type || isWireGroupType(alt.type)) return null;
|
|
12
|
-
const f = {
|
|
13
|
-
dimension: alt.column,
|
|
14
|
-
operator: alt.type,
|
|
15
|
-
expression: alt.type === "between" ? alt.from ?? "" : alt.value ?? ""
|
|
16
|
-
};
|
|
17
|
-
if (alt.type === "between" && alt.to) f.expression2 = alt.to;
|
|
18
|
-
return f;
|
|
25
|
+
function isRecord(value) {
|
|
26
|
+
return value !== null && typeof value === "object" && !Array.isArray(value);
|
|
19
27
|
}
|
|
20
|
-
function
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
}
|
|
28
|
+
function isStringArray(value) {
|
|
29
|
+
return Array.isArray(value) && value.every((item) => typeof item === "string");
|
|
30
|
+
}
|
|
31
|
+
function isCalendarDate(value) {
|
|
32
|
+
if (!/^\d{4}-\d{2}-\d{2}$/.test(value)) return false;
|
|
33
|
+
const parsed = /* @__PURE__ */ new Date(`${value}T00:00:00Z`);
|
|
34
|
+
return Number.isFinite(parsed.getTime()) && parsed.toISOString().slice(0, 10) === value;
|
|
35
|
+
}
|
|
36
|
+
function parseFilterResult(input, ancestors = /* @__PURE__ */ new Set()) {
|
|
37
|
+
if (input === void 0) return ok(void 0);
|
|
38
|
+
if (!isRecord(input) || ancestors.has(input)) return err(queryErrors.malformedFilterLeaf());
|
|
39
|
+
ancestors.add(input);
|
|
40
|
+
const invalid = () => err(queryErrors.malformedFilterLeaf());
|
|
25
41
|
const leaves = [];
|
|
26
42
|
const nested = [];
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
43
|
+
const wire = !("_filters" in input);
|
|
44
|
+
const groupType = wire ? input.type : input._groupType;
|
|
45
|
+
const isGroup = groupType === "and" || groupType === "or";
|
|
46
|
+
if (!wire && groupType !== void 0 && !isGroup) return invalid();
|
|
47
|
+
const values = wire ? isGroup ? input.filters : [input] : input._filters;
|
|
48
|
+
if (!Array.isArray(values)) return invalid();
|
|
49
|
+
for (const value of values) {
|
|
50
|
+
if (!isRecord(value)) return invalid();
|
|
51
|
+
if (wire && (value.type === "and" || value.type === "or")) {
|
|
52
|
+
const parsed = parseFilterResult(value, ancestors);
|
|
53
|
+
if (!parsed.ok) return parsed;
|
|
54
|
+
if (parsed.value) nested.push(parsed.value);
|
|
55
|
+
continue;
|
|
56
|
+
}
|
|
57
|
+
const dimension = wire ? value.column : value.dimension;
|
|
58
|
+
const operator = wire ? value.type === "eq" ? "equals" : value.type === "ne" ? "notEquals" : value.type : value.operator;
|
|
59
|
+
const between = operator === "between" || operator === "metricBetween";
|
|
60
|
+
const expression = wire ? between ? value.from : value.value : value.expression;
|
|
61
|
+
const expression2 = wire ? between ? value.to : void 0 : value.expression2;
|
|
62
|
+
if (typeof dimension !== "string" || !dimension || typeof operator !== "string" || !operator || typeof expression !== "string" || expression2 !== void 0 && typeof expression2 !== "string" || between && expression2 === void 0) return invalid();
|
|
63
|
+
if (!KNOWN_DIMENSIONS.has(dimension) && !isMetric(dimension) && !isQueryParam(dimension)) return invalid();
|
|
64
|
+
if (!FILTER_OPERATORS.has(operator) && !isDateOperator(operator) && !isMetricOperator(operator) && !isSpecialOperator(operator)) return invalid();
|
|
65
|
+
if (dimension === "searchType" && (operator !== "equals" || !KNOWN_SEARCH_TYPES.has(expression))) return invalid();
|
|
66
|
+
if (isDateOperator(operator) && (dimension !== "date" || !isCalendarDate(expression) || expression2 !== void 0 && !isCalendarDate(expression2))) return invalid();
|
|
67
|
+
if (isMetricOperator(operator) && (!isMetric(dimension) || !expression.trim() || !Number.isFinite(Number(expression)) || expression2 !== void 0 && (!expression2.trim() || !Number.isFinite(Number(expression2))))) return invalid();
|
|
68
|
+
leaves.push({
|
|
69
|
+
dimension,
|
|
70
|
+
operator,
|
|
71
|
+
expression,
|
|
72
|
+
...expression2 !== void 0 ? { expression2 } : {}
|
|
73
|
+
});
|
|
33
74
|
}
|
|
34
|
-
if (
|
|
35
|
-
|
|
75
|
+
if (!wire && input._nestedGroups !== void 0) {
|
|
76
|
+
if (!Array.isArray(input._nestedGroups)) return invalid();
|
|
77
|
+
for (const group of input._nestedGroups) {
|
|
78
|
+
const parsed = parseFilterResult(group, ancestors);
|
|
79
|
+
if (!parsed.ok) return parsed;
|
|
80
|
+
if (!parsed.value) return invalid();
|
|
81
|
+
nested.push(parsed.value);
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
if (groupType === "or" && (leaves.length === 0 || nested.length > 0 || leaves.some((leaf) => leaf.dimension === "searchType" || leaf.dimension === "date" && isDateOperator(leaf.operator)))) return invalid();
|
|
85
|
+
ancestors.delete(input);
|
|
86
|
+
return ok({
|
|
36
87
|
_filters: leaves,
|
|
37
|
-
|
|
38
|
-
_groupType:
|
|
39
|
-
};
|
|
40
|
-
}
|
|
41
|
-
function isWireFilter(input) {
|
|
42
|
-
if (!input || typeof input !== "object") return false;
|
|
43
|
-
const o = input;
|
|
44
|
-
if ("_filters" in o) return false;
|
|
45
|
-
return "type" in o && typeof o.type === "string" || "filters" in o && Array.isArray(o.filters);
|
|
88
|
+
...nested.length ? { _nestedGroups: nested } : {},
|
|
89
|
+
...isGroup ? { _groupType: groupType } : {}
|
|
90
|
+
});
|
|
46
91
|
}
|
|
47
92
|
function normalizeFilter(input) {
|
|
48
|
-
|
|
49
|
-
if (isWireFilter(input)) return convertWireGroup(input) ?? void 0;
|
|
50
|
-
if (typeof input === "object" && Array.isArray(input._filters)) return input;
|
|
93
|
+
return unwrapResult(parseFilterResult(input), queryErrorToException);
|
|
51
94
|
}
|
|
52
95
|
function normalizeOrderBy(orderBy) {
|
|
96
|
+
if (Array.isArray(orderBy) && orderBy.length !== 1) return void 0;
|
|
53
97
|
const spec = Array.isArray(orderBy) ? orderBy[0] : orderBy;
|
|
54
98
|
if (!spec || typeof spec !== "object") return void 0;
|
|
55
99
|
const o = spec;
|
|
56
|
-
if (typeof o.column !== "string" || o.column.
|
|
100
|
+
if (typeof o.column !== "string" || !isMetric(o.column) && o.column !== "date") return void 0;
|
|
101
|
+
if (o.dir !== void 0 && (typeof o.dir !== "string" || !["asc", "desc"].includes(o.dir.toLowerCase()))) return void 0;
|
|
102
|
+
if (o.desc !== void 0 && typeof o.desc !== "boolean") return void 0;
|
|
57
103
|
const dir = typeof o.dir === "string" ? o.dir.toLowerCase() === "asc" ? "asc" : "desc" : o.desc === false ? "asc" : "desc";
|
|
104
|
+
if (typeof o.desc === "boolean" && o.desc !== (dir === "desc")) return void 0;
|
|
58
105
|
return {
|
|
59
106
|
column: o.column,
|
|
60
107
|
dir
|
|
61
108
|
};
|
|
62
109
|
}
|
|
63
|
-
function hasMalformedFilterLeaf(filter) {
|
|
64
|
-
if (!filter || typeof filter !== "object") return false;
|
|
65
|
-
if (Array.isArray(filter._filters)) {
|
|
66
|
-
for (const leaf of filter._filters) if (!leaf || typeof leaf !== "object" || typeof leaf.operator !== "string" || typeof leaf.dimension !== "string") return true;
|
|
67
|
-
}
|
|
68
|
-
if (Array.isArray(filter._nestedGroups)) {
|
|
69
|
-
for (const group of filter._nestedGroups) if (hasMalformedFilterLeaf(group)) return true;
|
|
70
|
-
}
|
|
71
|
-
return false;
|
|
72
|
-
}
|
|
73
110
|
function normalizeBuilderStateResult(state) {
|
|
74
|
-
if (!state
|
|
111
|
+
if (!isRecord(state)) return err(queryErrors.invalidBuilderState(state));
|
|
75
112
|
const s = state;
|
|
76
|
-
const
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
113
|
+
const invalidState = (message) => err({
|
|
114
|
+
...queryErrors.invalidBuilderState(state),
|
|
115
|
+
message
|
|
116
|
+
});
|
|
117
|
+
if (s.dimensions !== void 0 && (!isStringArray(s.dimensions) || !s.dimensions.every((dimension) => KNOWN_DIMENSIONS.has(dimension)))) return invalidState(`dimensions must contain valid names: ${[...KNOWN_DIMENSIONS].join(", ")}.`);
|
|
118
|
+
if (s.metrics !== void 0 && (!isStringArray(s.metrics) || !s.metrics.every(isMetric))) return invalidState("metrics must contain clicks, impressions, ctr, or position.");
|
|
119
|
+
if (s.searchType !== void 0 && (typeof s.searchType !== "string" || !KNOWN_SEARCH_TYPES.has(s.searchType))) return invalidState(`searchType must be one of: ${[...KNOWN_SEARCH_TYPES].join(", ")}.`);
|
|
120
|
+
if (s.dataState !== void 0 && ![
|
|
121
|
+
"all",
|
|
122
|
+
"final",
|
|
123
|
+
"hourly_all"
|
|
124
|
+
].includes(s.dataState)) return invalidState("dataState must be all, final, or hourly_all.");
|
|
125
|
+
if (s.aggregationType !== void 0 && ![
|
|
126
|
+
"auto",
|
|
127
|
+
"byPage",
|
|
128
|
+
"byProperty",
|
|
129
|
+
"byNewsShowcasePanel"
|
|
130
|
+
].includes(s.aggregationType)) return invalidState("aggregationType must be auto, byPage, byProperty, or byNewsShowcasePanel.");
|
|
131
|
+
if (s.rowLimit !== void 0 && (!Number.isSafeInteger(s.rowLimit) || s.rowLimit < 1)) return err(queryErrors.invalidRowLimit(s.rowLimit));
|
|
132
|
+
if (s.startRow !== void 0 && (!Number.isSafeInteger(s.startRow) || s.startRow < 0)) return err(queryErrors.invalidStartRow(s.startRow));
|
|
133
|
+
const orderBy = normalizeOrderBy(s.orderBy);
|
|
134
|
+
if (s.orderBy !== void 0 && orderBy === void 0) return invalidState("orderBy requires a metric or date column and a consistent asc or desc direction.");
|
|
135
|
+
const filter = parseFilterResult(s.filter);
|
|
136
|
+
if (!filter.ok) return filter;
|
|
137
|
+
const prefilter = parseFilterResult(s.prefilter);
|
|
138
|
+
if (!prefilter.ok) return prefilter;
|
|
139
|
+
return ok({
|
|
140
|
+
dimensions: s.dimensions ?? [],
|
|
80
141
|
metrics: s.metrics,
|
|
81
|
-
filter,
|
|
82
|
-
|
|
142
|
+
filter: filter.value,
|
|
143
|
+
prefilter: prefilter.value,
|
|
144
|
+
orderBy,
|
|
83
145
|
rowLimit: s.rowLimit,
|
|
84
146
|
startRow: s.startRow,
|
|
85
147
|
dataState: s.dataState,
|
|
86
|
-
aggregationType: s.aggregationType
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
return ok(normalized);
|
|
148
|
+
aggregationType: s.aggregationType,
|
|
149
|
+
searchType: s.searchType
|
|
150
|
+
});
|
|
90
151
|
}
|
|
91
152
|
function normalizeBuilderState(state) {
|
|
92
153
|
return unwrapResult(normalizeBuilderStateResult(state), queryErrorToException);
|
|
@@ -167,7 +228,10 @@ function extractSearchType(state) {
|
|
|
167
228
|
if (typeof raw !== "string" || raw.length === 0) return void 0;
|
|
168
229
|
return KNOWN_SEARCH_TYPES.has(raw) ? raw : void 0;
|
|
169
230
|
}
|
|
170
|
-
function resolveToBodyResult(
|
|
231
|
+
function resolveToBodyResult(input) {
|
|
232
|
+
const parsed = normalizeBuilderStateResult(input);
|
|
233
|
+
if (!parsed.ok) return parsed;
|
|
234
|
+
const state = parsed.value;
|
|
171
235
|
const { startDate, endDate, searchType, dimensionFilter } = extractSpecialFilters(state.filter);
|
|
172
236
|
if (!startDate || !endDate) return err(queryErrors.missingDateRange());
|
|
173
237
|
const body = {
|
package/dist/query/types.d.mts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { GscAggregationType, GscDataState } from "../contracts.mjs";
|
|
2
2
|
import { Country, Device, SearchType } from "./constants.mjs";
|
|
3
|
-
interface DimensionValueMap {
|
|
3
|
+
export interface DimensionValueMap {
|
|
4
4
|
query: string;
|
|
5
5
|
queryCanonical: string;
|
|
6
6
|
page: string;
|
|
@@ -11,53 +11,53 @@ interface DimensionValueMap {
|
|
|
11
11
|
/** Hour bucket — ISO-8601 with PT offset, e.g. `2025-07-14T13:00:00-07:00`. Use with `dataState: 'hourly_all'`. */
|
|
12
12
|
hour: string;
|
|
13
13
|
}
|
|
14
|
-
type Dimension = keyof DimensionValueMap;
|
|
15
|
-
interface QueryParamValueMap {
|
|
14
|
+
export type Dimension = keyof DimensionValueMap;
|
|
15
|
+
export interface QueryParamValueMap {
|
|
16
16
|
searchType: SearchType;
|
|
17
17
|
}
|
|
18
|
-
type QueryParamName = keyof QueryParamValueMap;
|
|
19
|
-
interface Column<D extends Dimension> {
|
|
18
|
+
export type QueryParamName = keyof QueryParamValueMap;
|
|
19
|
+
export interface Column<D extends Dimension> {
|
|
20
20
|
readonly __columnBrand: 'gscdump.Column';
|
|
21
21
|
readonly dimension: D;
|
|
22
22
|
}
|
|
23
|
-
interface QueryParam<P extends QueryParamName> {
|
|
23
|
+
export interface QueryParam<P extends QueryParamName> {
|
|
24
24
|
readonly __queryParamBrand: 'gscdump.QueryParam';
|
|
25
25
|
readonly param: P;
|
|
26
26
|
}
|
|
27
|
-
type FilterOperator = 'equals' | 'notEquals' | 'contains' | 'notContains' | 'includingRegex' | 'excludingRegex';
|
|
28
|
-
type DateOperator = 'gte' | 'gt' | 'lte' | 'lt' | 'between';
|
|
29
|
-
type MetricOperator = 'metricGte' | 'metricGt' | 'metricLte' | 'metricLt' | 'metricBetween';
|
|
30
|
-
type SpecialOperator = 'topLevel';
|
|
31
|
-
interface InternalFilter {
|
|
27
|
+
export type FilterOperator = 'equals' | 'notEquals' | 'contains' | 'notContains' | 'includingRegex' | 'excludingRegex';
|
|
28
|
+
export type DateOperator = 'gte' | 'gt' | 'lte' | 'lt' | 'between';
|
|
29
|
+
export type MetricOperator = 'metricGte' | 'metricGt' | 'metricLte' | 'metricLt' | 'metricBetween';
|
|
30
|
+
export type SpecialOperator = 'topLevel';
|
|
31
|
+
export interface InternalFilter {
|
|
32
32
|
dimension: Dimension | QueryParamName | Metric;
|
|
33
33
|
operator: FilterOperator | DateOperator | MetricOperator | SpecialOperator;
|
|
34
34
|
expression: string;
|
|
35
35
|
expression2?: string;
|
|
36
36
|
}
|
|
37
|
-
interface Filter<C = object> {
|
|
37
|
+
export interface Filter<C = object> {
|
|
38
38
|
readonly __filterBrand: 'gscdump.Filter';
|
|
39
39
|
readonly _constraints: C;
|
|
40
40
|
readonly _filters: InternalFilter[];
|
|
41
41
|
readonly _nestedGroups?: Filter<any>[];
|
|
42
42
|
readonly _groupType?: 'and' | 'or';
|
|
43
43
|
}
|
|
44
|
-
type MergeConstraints<F extends Filter<any>[]> = UnionToIntersection<F[number]['_constraints']>;
|
|
45
|
-
type UnionToIntersection<U> = (U extends any ? (k: U) => void : never) extends ((k: infer I) => void) ? I : never;
|
|
46
|
-
interface GSCResult<D extends Dimension[], C> {
|
|
44
|
+
export type MergeConstraints<F extends Filter<any>[]> = UnionToIntersection<F[number]['_constraints']>;
|
|
45
|
+
export type UnionToIntersection<U> = (U extends any ? (k: U) => void : never) extends ((k: infer I) => void) ? I : never;
|
|
46
|
+
export interface GSCResult<D extends Dimension[], C> {
|
|
47
47
|
rows: Array<GSCRow<D, C>>;
|
|
48
48
|
}
|
|
49
|
-
type GSCRow<D extends Dimension[], C> = { [K in D[number]]: K extends keyof C ? C[K] : DimensionValueMap[K]; } & {
|
|
49
|
+
export type GSCRow<D extends Dimension[], C> = { [K in D[number]]: K extends keyof C ? C[K] : DimensionValueMap[K]; } & {
|
|
50
50
|
clicks: number;
|
|
51
51
|
impressions: number;
|
|
52
52
|
ctr: number;
|
|
53
53
|
position: number;
|
|
54
54
|
};
|
|
55
|
-
type Metric = 'clicks' | 'impressions' | 'ctr' | 'position';
|
|
56
|
-
interface MetricColumn<M extends Metric> {
|
|
55
|
+
export type Metric = 'clicks' | 'impressions' | 'ctr' | 'position';
|
|
56
|
+
export interface MetricColumn<M extends Metric> {
|
|
57
57
|
readonly __metricColumnBrand: 'gscdump.MetricColumn';
|
|
58
58
|
readonly metric: M;
|
|
59
59
|
}
|
|
60
|
-
interface BuilderState {
|
|
60
|
+
export interface BuilderState {
|
|
61
61
|
dimensions: Dimension[];
|
|
62
62
|
metrics?: Metric[];
|
|
63
63
|
filter?: Filter<any>;
|
|
@@ -75,16 +75,15 @@ interface BuilderState {
|
|
|
75
75
|
/** GSC search corpus. Wins over any `searchType` filter when both are set. */
|
|
76
76
|
searchType?: SearchType;
|
|
77
77
|
}
|
|
78
|
-
interface JsonInternalFilter {
|
|
78
|
+
export interface JsonInternalFilter {
|
|
79
79
|
dimension: string;
|
|
80
80
|
operator: string;
|
|
81
81
|
expression: string;
|
|
82
82
|
expression2?: string;
|
|
83
83
|
}
|
|
84
|
-
interface JsonFilter {
|
|
84
|
+
export interface JsonFilter {
|
|
85
85
|
_filters: JsonInternalFilter[];
|
|
86
86
|
_nestedGroups?: JsonFilter[];
|
|
87
87
|
_groupType?: 'and' | 'or';
|
|
88
88
|
}
|
|
89
|
-
type FilterInput = Filter<any> | JsonFilter;
|
|
90
|
-
export { BuilderState, Column, DateOperator, Dimension, DimensionValueMap, Filter, FilterInput, FilterOperator, GSCResult, GSCRow, InternalFilter, JsonFilter, JsonInternalFilter, MergeConstraints, Metric, MetricColumn, MetricOperator, QueryParam, QueryParamName, QueryParamValueMap, SpecialOperator, UnionToIntersection };
|
|
89
|
+
export type FilterInput = Filter<any> | JsonFilter;
|
|
@@ -1,3 +1,2 @@
|
|
|
1
|
-
declare function currentPstDate(): string;
|
|
2
|
-
declare function daysAgoPst(n: number): string;
|
|
3
|
-
export { currentPstDate, daysAgoPst };
|
|
1
|
+
export declare function currentPstDate(): string;
|
|
2
|
+
export declare function daysAgoPst(n: number): string;
|
|
@@ -1,31 +1,30 @@
|
|
|
1
|
-
type SitemapIdentityResult = {
|
|
1
|
+
export type SitemapIdentityResult = {
|
|
2
2
|
_tag: 'ok';
|
|
3
3
|
url: string;
|
|
4
4
|
} | {
|
|
5
5
|
_tag: 'invalid';
|
|
6
6
|
reason: 'credentials' | 'empty' | 'invalid_url' | 'off_origin';
|
|
7
7
|
};
|
|
8
|
-
interface SitemapEvidenceRecord {
|
|
8
|
+
export interface SitemapEvidenceRecord {
|
|
9
9
|
path: string;
|
|
10
10
|
lastDownloaded?: string | null;
|
|
11
11
|
fetchedAt?: number | null;
|
|
12
12
|
}
|
|
13
|
-
interface ScopedSitemapRecords<T extends SitemapEvidenceRecord> {
|
|
13
|
+
export interface ScopedSitemapRecords<T extends SitemapEvidenceRecord> {
|
|
14
14
|
sitemaps: Array<T & {
|
|
15
15
|
path: string;
|
|
16
16
|
}>;
|
|
17
17
|
excludedCount: number;
|
|
18
18
|
duplicateCount: number;
|
|
19
19
|
}
|
|
20
|
-
interface SitemapContentEntry {
|
|
20
|
+
export interface SitemapContentEntry {
|
|
21
21
|
loc: string;
|
|
22
22
|
}
|
|
23
|
-
declare function canonicalSitemapIdentity(path: string, site?: string): SitemapIdentityResult;
|
|
24
|
-
declare function sameSitemapIdentity(left: string, right: string, site?: string): boolean;
|
|
25
|
-
declare function scopeSitemapRecords<T extends SitemapEvidenceRecord>(records: readonly T[], site?: string): ScopedSitemapRecords<T>;
|
|
23
|
+
export declare function canonicalSitemapIdentity(path: string, site?: string): SitemapIdentityResult;
|
|
24
|
+
export declare function sameSitemapIdentity(left: string, right: string, site?: string): boolean;
|
|
25
|
+
export declare function scopeSitemapRecords<T extends SitemapEvidenceRecord>(records: readonly T[], site?: string): ScopedSitemapRecords<T>;
|
|
26
26
|
/**
|
|
27
27
|
* Versioned exact membership digest. This deliberately does not use the
|
|
28
28
|
* analytics-only `urlMatchKey` normalizer.
|
|
29
29
|
*/
|
|
30
|
-
declare function sitemapContentHash(entries: readonly SitemapContentEntry[]): Promise<string>;
|
|
31
|
-
export { ScopedSitemapRecords, SitemapContentEntry, SitemapEvidenceRecord, SitemapIdentityResult, canonicalSitemapIdentity, sameSitemapIdentity, scopeSitemapRecords, sitemapContentHash };
|
|
30
|
+
export declare function sitemapContentHash(entries: readonly SitemapContentEntry[]): Promise<string>;
|
package/dist/tenant.d.mts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
declare function encodeSiteId(siteUrl: string): string;
|
|
1
|
+
export declare function encodeSiteId(siteUrl: string): string;
|
|
2
2
|
/**
|
|
3
3
|
* Best-effort inverse of `encodeSiteId` for the common prefixes. Lossy
|
|
4
4
|
* (`encodeSiteId` collapses non-word chars to `_` and strips trailing
|
|
@@ -8,11 +8,10 @@ declare function encodeSiteId(siteUrl: string): string;
|
|
|
8
8
|
* Returns the input unchanged when neither prefix is recognised, so callers
|
|
9
9
|
* can pass through canonical site URLs without branching.
|
|
10
10
|
*/
|
|
11
|
-
declare function decodeSiteId(encoded: string): string;
|
|
11
|
+
export declare function decodeSiteId(encoded: string): string;
|
|
12
12
|
/**
|
|
13
13
|
* Normalize a siteUrl to the form Google APIs expect: domain properties get
|
|
14
14
|
* the `sc-domain:` prefix added if missing, URL properties pass through.
|
|
15
15
|
* Idempotent — safe to call on already-prefixed values.
|
|
16
16
|
*/
|
|
17
|
-
declare function normalizeSiteUrl(siteUrl: string): string;
|
|
18
|
-
export { decodeSiteId, encodeSiteId, normalizeSiteUrl };
|
|
17
|
+
export declare function normalizeSiteUrl(siteUrl: string): string;
|
package/dist/url.d.mts
CHANGED
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "gscdump",
|
|
3
3
|
"type": "module",
|
|
4
|
-
"version": "3.
|
|
4
|
+
"version": "3.6.0",
|
|
5
5
|
"description": "Direct Google Search Console and Bing Webmaster clients with typed queries and Indexing Evidence",
|
|
6
6
|
"author": {
|
|
7
7
|
"name": "Harlan Wilton",
|
|
@@ -113,7 +113,7 @@
|
|
|
113
113
|
"node": ">=22"
|
|
114
114
|
},
|
|
115
115
|
"dependencies": {
|
|
116
|
-
"@gscdump/contracts": "^3.
|
|
116
|
+
"@gscdump/contracts": "^3.6.0",
|
|
117
117
|
"ofetch": "^1.5.1"
|
|
118
118
|
},
|
|
119
119
|
"scripts": {
|