gscdump 1.0.1 → 1.0.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/_chunks/builder.d.mts +24 -0
- package/dist/_chunks/contracts.d.mts +47 -0
- package/dist/_chunks/dayjs.d.mts +3 -0
- package/dist/_chunks/dayjs.mjs +30 -0
- package/dist/_chunks/gsc-dates.d.mts +64 -0
- package/dist/_chunks/gsc-dates.mjs +100 -0
- package/dist/_chunks/plan.d.mts +175 -0
- package/dist/_chunks/resolver.mjs +1930 -0
- package/dist/_chunks/result.d.mts +20 -0
- package/dist/_chunks/types.d.mts +112 -0
- package/dist/contracts.d.mts +1 -46
- package/dist/core/result.d.mts +1 -19
- package/dist/dates.d.mts +2 -62
- package/dist/dates.mjs +2 -117
- package/dist/index.d.mts +7 -240
- package/dist/index.mjs +5 -1930
- package/dist/query/index.d.mts +6 -323
- package/dist/query/index.mjs +3 -2138
- package/dist/query/plan.d.mts +2 -234
- package/dist/query/plan.mjs +2 -1809
- package/package.json +2 -2
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import { GscAggregationType, GscDataState, GscSearchAnalyticsRequest, GscSearchType } from "./contracts.mjs";
|
|
2
|
+
import { BuilderState, Column, Dimension, Filter, Metric, MetricColumn } from "./types.mjs";
|
|
3
|
+
type SelectableColumn = Column<Dimension> | MetricColumn<Metric>;
|
|
4
|
+
type OrderableColumn = MetricColumn<Metric> | Column<'date'>;
|
|
5
|
+
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;
|
|
6
|
+
interface GSCQueryBuilder<D extends Dimension[] = [], C = object> {
|
|
7
|
+
select: {
|
|
8
|
+
<T extends Dimension[]>(...dims: T): GSCQueryBuilder<T, C>;
|
|
9
|
+
<T extends SelectableColumn[]>(...cols: T): GSCQueryBuilder<ExtractDimensions<T> & Dimension[], C>;
|
|
10
|
+
};
|
|
11
|
+
where: <F extends Filter<any>>(filter: F) => GSCQueryBuilder<D, C & F['_constraints']>;
|
|
12
|
+
prefilter: <F extends Filter<any>>(filter: F) => GSCQueryBuilder<D, C>;
|
|
13
|
+
orderBy: (col: OrderableColumn, dir: 'asc' | 'desc') => GSCQueryBuilder<D, C>;
|
|
14
|
+
limit: (n: number) => GSCQueryBuilder<D, C>;
|
|
15
|
+
offset: (n: number) => GSCQueryBuilder<D, C>;
|
|
16
|
+
dataState: (state: GscDataState) => GSCQueryBuilder<D, C>;
|
|
17
|
+
aggregationType: (type: GscAggregationType) => GSCQueryBuilder<D, C>;
|
|
18
|
+
/** GSC search corpus (`type` on the wire). Equivalent to filtering by `searchType`. */
|
|
19
|
+
type: (t: GscSearchType) => GSCQueryBuilder<D, C>;
|
|
20
|
+
toBody: () => GscSearchAnalyticsRequest;
|
|
21
|
+
getState: () => BuilderState;
|
|
22
|
+
}
|
|
23
|
+
declare const gsc: GSCQueryBuilder<[], object>;
|
|
24
|
+
export { GSCQueryBuilder, gsc };
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
import { ColumnDef, ColumnType, Row, TableName, TableSchema, TenantCtx } from "@gscdump/contracts";
|
|
2
|
+
type GscSearchAnalyticsDimension = 'page' | 'query' | 'country' | 'device' | 'date' | 'hour' | 'searchAppearance';
|
|
3
|
+
type GscDataState = 'final' | 'all' | 'hourly_all';
|
|
4
|
+
interface GscSearchAnalyticsMetadata {
|
|
5
|
+
/** First date (YYYY-MM-DD, PT) still being collected. Populated when dataState=`all` and grouped by `date`. */
|
|
6
|
+
first_incomplete_date?: string;
|
|
7
|
+
/** First hour (YYYY-MM-DDThh:mm:ss±hh:mm, PT) still being collected. Populated when dataState=`hourly_all` and grouped by `hour`. */
|
|
8
|
+
first_incomplete_hour?: string;
|
|
9
|
+
}
|
|
10
|
+
type GscSearchAnalyticsFilterOperator = 'equals' | 'notEquals' | 'contains' | 'notContains' | 'includingRegex' | 'excludingRegex';
|
|
11
|
+
interface GscSearchAnalyticsFilter {
|
|
12
|
+
dimension: GscSearchAnalyticsDimension;
|
|
13
|
+
expression: string;
|
|
14
|
+
operator?: GscSearchAnalyticsFilterOperator;
|
|
15
|
+
}
|
|
16
|
+
interface GscSearchAnalyticsFilterGroup {
|
|
17
|
+
groupType?: 'and' | 'or';
|
|
18
|
+
filters: GscSearchAnalyticsFilter[];
|
|
19
|
+
}
|
|
20
|
+
type GscSearchType = 'web' | 'image' | 'video' | 'news' | 'discover' | 'googleNews';
|
|
21
|
+
type GscAggregationType = 'auto' | 'byPage' | 'byProperty' | 'byNewsShowcasePanel';
|
|
22
|
+
type GscResponseAggregationType = 'auto' | 'byPage' | 'byProperty' | 'byNewsShowcasePanel';
|
|
23
|
+
interface GscSearchAnalyticsRequest {
|
|
24
|
+
startDate: string;
|
|
25
|
+
endDate: string;
|
|
26
|
+
dimensions?: GscSearchAnalyticsDimension[];
|
|
27
|
+
dimensionFilterGroups?: GscSearchAnalyticsFilterGroup[];
|
|
28
|
+
rowLimit?: number;
|
|
29
|
+
startRow?: number;
|
|
30
|
+
/** GSC search corpus. Maps to wire field `type` (the API still accepts the deprecated `searchType` alias). */
|
|
31
|
+
type?: GscSearchType;
|
|
32
|
+
dataState?: GscDataState;
|
|
33
|
+
aggregationType?: GscAggregationType;
|
|
34
|
+
}
|
|
35
|
+
interface GscSearchAnalyticsRow {
|
|
36
|
+
keys: string[];
|
|
37
|
+
clicks: number;
|
|
38
|
+
impressions: number;
|
|
39
|
+
ctr: number;
|
|
40
|
+
position: number;
|
|
41
|
+
}
|
|
42
|
+
interface GscSearchAnalyticsResponse {
|
|
43
|
+
rows?: GscSearchAnalyticsRow[];
|
|
44
|
+
responseAggregationType?: GscResponseAggregationType;
|
|
45
|
+
metadata?: GscSearchAnalyticsMetadata;
|
|
46
|
+
}
|
|
47
|
+
export { type ColumnDef, type ColumnType, GscAggregationType, GscDataState, GscResponseAggregationType, GscSearchAnalyticsDimension, GscSearchAnalyticsFilter, GscSearchAnalyticsFilterGroup, GscSearchAnalyticsFilterOperator, GscSearchAnalyticsMetadata, GscSearchAnalyticsRequest, GscSearchAnalyticsResponse, GscSearchAnalyticsRow, GscSearchType, type Row, type TableName, type TableSchema, type TenantCtx };
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import { format } from "date-fns/format";
|
|
2
|
+
import { subDays } from "date-fns/subDays";
|
|
3
|
+
let pstFormatter;
|
|
4
|
+
function getPstFormatter() {
|
|
5
|
+
pstFormatter ??= new Intl.DateTimeFormat("en-CA", {
|
|
6
|
+
timeZone: "America/Los_Angeles",
|
|
7
|
+
year: "numeric",
|
|
8
|
+
month: "2-digit",
|
|
9
|
+
day: "2-digit"
|
|
10
|
+
});
|
|
11
|
+
return pstFormatter;
|
|
12
|
+
}
|
|
13
|
+
function pstDateParts(d = /* @__PURE__ */ new Date()) {
|
|
14
|
+
const parts = getPstFormatter().formatToParts(d);
|
|
15
|
+
const get = (t) => Number(parts.find((p) => p.type === t).value);
|
|
16
|
+
return {
|
|
17
|
+
year: get("year"),
|
|
18
|
+
month: get("month"),
|
|
19
|
+
day: get("day")
|
|
20
|
+
};
|
|
21
|
+
}
|
|
22
|
+
function currentPstDate() {
|
|
23
|
+
const { year, month, day } = pstDateParts();
|
|
24
|
+
return `${year}-${String(month).padStart(2, "0")}-${String(day).padStart(2, "0")}`;
|
|
25
|
+
}
|
|
26
|
+
function daysAgoPst(n) {
|
|
27
|
+
const { year, month, day } = pstDateParts();
|
|
28
|
+
return format(subDays(new Date(year, month - 1, day, 12), n), "yyyy-MM-dd");
|
|
29
|
+
}
|
|
30
|
+
export { currentPstDate, daysAgoPst };
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
/** Milliseconds in a UTC day. Use for date arithmetic without DST surprises. */
|
|
2
|
+
declare const MS_PER_DAY = 86400000;
|
|
3
|
+
/** Format a Date as YYYY-MM-DD (UTC). */
|
|
4
|
+
declare function toIsoDate(d: Date): string;
|
|
5
|
+
/** Most recent date GSC has fully finalized data for (no further updates). */
|
|
6
|
+
declare const GSC_FINALIZED_LAG_DAYS = 3;
|
|
7
|
+
/** Most recent date GSC will return data for, may still be updating. */
|
|
8
|
+
declare const GSC_FRESHEST_LAG_DAYS = 1;
|
|
9
|
+
/** Approximate historical retention window for Search Analytics. */
|
|
10
|
+
declare const GSC_RETENTION_MONTHS = 16;
|
|
11
|
+
/** Today's date (YYYY-MM-DD) in PST. */
|
|
12
|
+
declare function getPstDate(): string;
|
|
13
|
+
/** YYYY-MM-DD for `now() - n` days, UTC. */
|
|
14
|
+
declare function daysAgo(n: number): string;
|
|
15
|
+
/** Most recent finalized date (3 days ago PST). */
|
|
16
|
+
declare function getLatestGscDate(): string;
|
|
17
|
+
/** Freshest date GSC will return (yesterday PST, may still update). */
|
|
18
|
+
declare function getFreshestGscDate(): string;
|
|
19
|
+
/**
|
|
20
|
+
* Dates that are still pending (1-3 days ago PST). These need to be re-synced
|
|
21
|
+
* each day until they finalize.
|
|
22
|
+
*/
|
|
23
|
+
declare function getPendingDates(): string[];
|
|
24
|
+
/** All dates between two YYYY-MM-DD strings (inclusive, oldest first). */
|
|
25
|
+
declare function getDateRange(startDate: string, endDate: string): string[];
|
|
26
|
+
/** Default span used when chunking long backfills into batches. */
|
|
27
|
+
declare const DAYS_PER_RANGE = 30;
|
|
28
|
+
/**
|
|
29
|
+
* Like {@link getDateRange} but skips dates outside GSC's queryable window
|
|
30
|
+
* (older than retention or newer than the freshest date).
|
|
31
|
+
*/
|
|
32
|
+
declare function generateGscDateRange(startDate: string, endDate: string): string[];
|
|
33
|
+
/**
|
|
34
|
+
* Group a sorted list of dates into contiguous runs, splitting on either a
|
|
35
|
+
* gap or after `daysPerRange` dates accumulate. Useful for bucketing
|
|
36
|
+
* backfill work into bounded jobs.
|
|
37
|
+
*/
|
|
38
|
+
declare function groupIntoRanges(dates: string[], daysPerRange?: number): Array<{
|
|
39
|
+
startDate: string;
|
|
40
|
+
endDate: string;
|
|
41
|
+
}>;
|
|
42
|
+
/** Inclusive day count between two YYYY-MM-DD strings. */
|
|
43
|
+
declare function countDays(startDate: string, endDate: string): number;
|
|
44
|
+
/** Oldest date GSC retains (~16 months ago). */
|
|
45
|
+
declare function getOldestGscDate(): string;
|
|
46
|
+
/** Add `n` UTC days to a YYYY-MM-DD string (n may be negative). */
|
|
47
|
+
declare function addDays(dateStr: string, n: number): string;
|
|
48
|
+
declare function getPreviousDate(dateStr: string): string;
|
|
49
|
+
declare function getNextDate(dateStr: string): string;
|
|
50
|
+
/** True if `dateStr` falls within GSC's queryable window. */
|
|
51
|
+
declare function isValidGscDate(dateStr: string): boolean;
|
|
52
|
+
interface BackfillProgress {
|
|
53
|
+
progress: number;
|
|
54
|
+
daysAvailable: number;
|
|
55
|
+
daysSynced: number;
|
|
56
|
+
oldestGscDate: string;
|
|
57
|
+
isComplete: boolean;
|
|
58
|
+
}
|
|
59
|
+
/**
|
|
60
|
+
* Backfill progress (0-1) given the oldest + newest dates synced.
|
|
61
|
+
* Returns null when no sync data exists.
|
|
62
|
+
*/
|
|
63
|
+
declare function getBackfillProgress(oldestDateSynced: string | null, newestDateSynced: string | null): BackfillProgress | null;
|
|
64
|
+
export { BackfillProgress, DAYS_PER_RANGE, GSC_FINALIZED_LAG_DAYS, GSC_FRESHEST_LAG_DAYS, GSC_RETENTION_MONTHS, MS_PER_DAY, addDays, countDays, daysAgo, generateGscDateRange, getBackfillProgress, getDateRange, getFreshestGscDate, getLatestGscDate, getNextDate, getOldestGscDate, getPendingDates, getPreviousDate, getPstDate, groupIntoRanges, isValidGscDate, toIsoDate };
|
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
const MS_PER_DAY = 864e5;
|
|
2
|
+
function toIsoDate(d) {
|
|
3
|
+
return d.toISOString().slice(0, 10);
|
|
4
|
+
}
|
|
5
|
+
const GSC_FINALIZED_LAG_DAYS = 3;
|
|
6
|
+
const GSC_FRESHEST_LAG_DAYS = 1;
|
|
7
|
+
const GSC_RETENTION_MONTHS = 16;
|
|
8
|
+
function getPstDate() {
|
|
9
|
+
return (/* @__PURE__ */ new Date()).toLocaleDateString("en-CA", { timeZone: "America/Los_Angeles" });
|
|
10
|
+
}
|
|
11
|
+
function getPstDateDaysAgo(daysAgo) {
|
|
12
|
+
const pstNow = new Date((/* @__PURE__ */ new Date()).toLocaleString("en-US", { timeZone: "America/Los_Angeles" }));
|
|
13
|
+
pstNow.setDate(pstNow.getDate() - daysAgo);
|
|
14
|
+
return toIsoDate(pstNow);
|
|
15
|
+
}
|
|
16
|
+
function daysAgo(n) {
|
|
17
|
+
return toIsoDate(/* @__PURE__ */ new Date(Date.now() - n * MS_PER_DAY));
|
|
18
|
+
}
|
|
19
|
+
function getLatestGscDate() {
|
|
20
|
+
return getPstDateDaysAgo(3);
|
|
21
|
+
}
|
|
22
|
+
function getFreshestGscDate() {
|
|
23
|
+
return getPstDateDaysAgo(1);
|
|
24
|
+
}
|
|
25
|
+
function getPendingDates() {
|
|
26
|
+
const dates = [];
|
|
27
|
+
for (let daysAgo = 1; daysAgo <= 3; daysAgo++) dates.push(getPstDateDaysAgo(daysAgo));
|
|
28
|
+
return dates;
|
|
29
|
+
}
|
|
30
|
+
function getDateRange(startDate, endDate) {
|
|
31
|
+
const dates = [];
|
|
32
|
+
const endMs = Date.parse(`${endDate}T00:00:00Z`);
|
|
33
|
+
for (let cursor = Date.parse(`${startDate}T00:00:00Z`); cursor <= endMs; cursor += MS_PER_DAY) dates.push(toIsoDate(new Date(cursor)));
|
|
34
|
+
return dates;
|
|
35
|
+
}
|
|
36
|
+
const DAYS_PER_RANGE = 30;
|
|
37
|
+
function generateGscDateRange(startDate, endDate) {
|
|
38
|
+
const start = startDate < getOldestGscDate() ? getOldestGscDate() : startDate;
|
|
39
|
+
const end = endDate > getFreshestGscDate() ? getFreshestGscDate() : endDate;
|
|
40
|
+
return start <= end ? getDateRange(start, end) : [];
|
|
41
|
+
}
|
|
42
|
+
function groupIntoRanges(dates, daysPerRange = 30) {
|
|
43
|
+
if (dates.length === 0) return [];
|
|
44
|
+
const sorted = [...dates].sort();
|
|
45
|
+
const ranges = [];
|
|
46
|
+
let rangeStart = sorted[0];
|
|
47
|
+
let rangePrev = sorted[0];
|
|
48
|
+
for (let i = 1; i < sorted.length; i++) {
|
|
49
|
+
const current = sorted[i];
|
|
50
|
+
const expected = getNextDate(rangePrev);
|
|
51
|
+
const daysInRange = countDays(rangeStart, rangePrev);
|
|
52
|
+
if (current !== expected || daysInRange >= daysPerRange) {
|
|
53
|
+
ranges.push({
|
|
54
|
+
startDate: rangeStart,
|
|
55
|
+
endDate: rangePrev
|
|
56
|
+
});
|
|
57
|
+
rangeStart = current;
|
|
58
|
+
}
|
|
59
|
+
rangePrev = current;
|
|
60
|
+
}
|
|
61
|
+
ranges.push({
|
|
62
|
+
startDate: rangeStart,
|
|
63
|
+
endDate: rangePrev
|
|
64
|
+
});
|
|
65
|
+
return ranges;
|
|
66
|
+
}
|
|
67
|
+
function countDays(startDate, endDate) {
|
|
68
|
+
return Math.round((Date.parse(`${endDate}T00:00:00Z`) - Date.parse(`${startDate}T00:00:00Z`)) / MS_PER_DAY) + 1;
|
|
69
|
+
}
|
|
70
|
+
function getOldestGscDate() {
|
|
71
|
+
const date = /* @__PURE__ */ new Date();
|
|
72
|
+
date.setMonth(date.getMonth() - 16);
|
|
73
|
+
return toIsoDate(date);
|
|
74
|
+
}
|
|
75
|
+
function addDays(dateStr, n) {
|
|
76
|
+
return toIsoDate(new Date(Date.parse(`${dateStr}T00:00:00Z`) + n * MS_PER_DAY));
|
|
77
|
+
}
|
|
78
|
+
function getPreviousDate(dateStr) {
|
|
79
|
+
return addDays(dateStr, -1);
|
|
80
|
+
}
|
|
81
|
+
function getNextDate(dateStr) {
|
|
82
|
+
return addDays(dateStr, 1);
|
|
83
|
+
}
|
|
84
|
+
function isValidGscDate(dateStr) {
|
|
85
|
+
return dateStr >= getOldestGscDate() && dateStr <= getFreshestGscDate();
|
|
86
|
+
}
|
|
87
|
+
function getBackfillProgress(oldestDateSynced, newestDateSynced) {
|
|
88
|
+
if (!oldestDateSynced || !newestDateSynced) return null;
|
|
89
|
+
const oldestGsc = getOldestGscDate();
|
|
90
|
+
const totalDays = countDays(oldestGsc, getLatestGscDate());
|
|
91
|
+
const syncedDays = countDays(oldestDateSynced, newestDateSynced);
|
|
92
|
+
return {
|
|
93
|
+
progress: Math.round(Math.min(1, syncedDays / totalDays) * 100) / 100,
|
|
94
|
+
daysAvailable: totalDays,
|
|
95
|
+
daysSynced: syncedDays,
|
|
96
|
+
oldestGscDate: oldestGsc,
|
|
97
|
+
isComplete: oldestDateSynced <= oldestGsc
|
|
98
|
+
};
|
|
99
|
+
}
|
|
100
|
+
export { DAYS_PER_RANGE, GSC_FINALIZED_LAG_DAYS, GSC_FRESHEST_LAG_DAYS, GSC_RETENTION_MONTHS, MS_PER_DAY, addDays, countDays, daysAgo, generateGscDateRange, getBackfillProgress, getDateRange, getFreshestGscDate, getLatestGscDate, getNextDate, getOldestGscDate, getPendingDates, getPreviousDate, getPstDate, groupIntoRanges, isValidGscDate, toIsoDate };
|
|
@@ -0,0 +1,175 @@
|
|
|
1
|
+
import { TableName } from "./contracts.mjs";
|
|
2
|
+
import { Result } from "./result.mjs";
|
|
3
|
+
import { BuilderState, Dimension, FilterOperator, Metric, MetricOperator, QueryParamName } from "./types.mjs";
|
|
4
|
+
type QueryErrorKind = 'missing-date-range' | 'invalid-row-limit' | 'invalid-start-row' | 'invalid-data-state' | 'invalid-aggregation-type' | 'invalid-builder-state' | 'invalid-filter' | 'unsupported-capability' | 'unresolvable-dataset';
|
|
5
|
+
type QueryError = {
|
|
6
|
+
kind: 'missing-date-range';
|
|
7
|
+
message: string;
|
|
8
|
+
} | {
|
|
9
|
+
kind: 'invalid-row-limit';
|
|
10
|
+
value: unknown;
|
|
11
|
+
message: string;
|
|
12
|
+
} | {
|
|
13
|
+
kind: 'invalid-start-row';
|
|
14
|
+
value: unknown;
|
|
15
|
+
message: string;
|
|
16
|
+
} | {
|
|
17
|
+
kind: 'invalid-data-state';
|
|
18
|
+
message: string;
|
|
19
|
+
} | {
|
|
20
|
+
kind: 'invalid-aggregation-type';
|
|
21
|
+
message: string;
|
|
22
|
+
} | {
|
|
23
|
+
kind: 'invalid-builder-state';
|
|
24
|
+
message: string;
|
|
25
|
+
cause?: unknown;
|
|
26
|
+
} | {
|
|
27
|
+
kind: 'invalid-filter';
|
|
28
|
+
message: string;
|
|
29
|
+
} | {
|
|
30
|
+
kind: 'unsupported-capability';
|
|
31
|
+
capability: string;
|
|
32
|
+
context: string;
|
|
33
|
+
message: string;
|
|
34
|
+
} | {
|
|
35
|
+
kind: 'unresolvable-dataset';
|
|
36
|
+
dimensions: readonly Dimension[];
|
|
37
|
+
filterDims: readonly Dimension[];
|
|
38
|
+
message: string;
|
|
39
|
+
};
|
|
40
|
+
declare const queryErrors: {
|
|
41
|
+
readonly missingDateRange: () => QueryError;
|
|
42
|
+
readonly invalidRowLimit: (value: unknown) => QueryError;
|
|
43
|
+
readonly invalidStartRow: (value: unknown) => QueryError;
|
|
44
|
+
readonly hourDimensionRequiresHourlyState: () => QueryError;
|
|
45
|
+
readonly hourlyStateRequiresHourDimension: () => QueryError;
|
|
46
|
+
readonly byPropertyUnsupportedSearchType: () => QueryError;
|
|
47
|
+
readonly byPropertyNotAllowedWithPage: () => QueryError;
|
|
48
|
+
readonly byNewsShowcaseRequiresSearchType: () => QueryError;
|
|
49
|
+
readonly byNewsShowcaseNotAllowedWithPage: () => QueryError;
|
|
50
|
+
readonly byNewsShowcaseRequiresShowcaseFilter: () => QueryError;
|
|
51
|
+
readonly invalidBuilderState: (cause?: unknown) => QueryError;
|
|
52
|
+
readonly malformedFilterLeaf: () => QueryError;
|
|
53
|
+
readonly unsupportedCapability: (capability: string, context: string) => QueryError;
|
|
54
|
+
readonly unresolvableDataset: (dimensions: readonly Dimension[], filterDims?: readonly Dimension[]) => QueryError;
|
|
55
|
+
};
|
|
56
|
+
declare function isQueryError(value: unknown): value is QueryError;
|
|
57
|
+
/**
|
|
58
|
+
* Thrown when a query needs a planner capability (regex pushdown, comparison
|
|
59
|
+
* joins, multi-dataset reads) the target engine lacks. Engines catch it to fall
|
|
60
|
+
* back to the live GSC API. Carries the typed `queryError` value so a caller can
|
|
61
|
+
* read the modelled failure instead of parsing the message.
|
|
62
|
+
*/
|
|
63
|
+
declare class UnsupportedLogicalCapabilityError extends Error {
|
|
64
|
+
readonly queryError: Extract<QueryError, {
|
|
65
|
+
kind: 'unsupported-capability';
|
|
66
|
+
}>;
|
|
67
|
+
constructor(capability: string, context: string);
|
|
68
|
+
}
|
|
69
|
+
/**
|
|
70
|
+
* Thrown when a query's grouped + filtered dimensions span more than one stored
|
|
71
|
+
* dataset. Replaces the resolver's raw "unknown column" error so hosts can map
|
|
72
|
+
* it to a 4xx instead of leaking an opaque 500. Carries the typed `queryError`.
|
|
73
|
+
*/
|
|
74
|
+
declare class UnresolvableDatasetError extends Error {
|
|
75
|
+
readonly queryError: Extract<QueryError, {
|
|
76
|
+
kind: 'unresolvable-dataset';
|
|
77
|
+
}>;
|
|
78
|
+
constructor(dimensions: readonly Dimension[], filterDims?: readonly Dimension[]);
|
|
79
|
+
}
|
|
80
|
+
/**
|
|
81
|
+
* Re-raises a `QueryError` value as an `Error`, preserving the historical class
|
|
82
|
+
* identity for the two errors engines match on by name (`UnresolvableDatasetError`,
|
|
83
|
+
* `UnsupportedLogicalCapabilityError`). Pairs with `unwrapResult` so a
|
|
84
|
+
* `fooResult(): Result<A, QueryError>` core can back a throwing `foo()`.
|
|
85
|
+
*/
|
|
86
|
+
declare function queryErrorToException(error: QueryError): Error;
|
|
87
|
+
type LogicalDataset = TableName;
|
|
88
|
+
type ComparisonFilter = 'new' | 'lost' | 'improving' | 'declining';
|
|
89
|
+
interface PlannerCapabilities {
|
|
90
|
+
regex?: boolean;
|
|
91
|
+
multiDataset?: boolean;
|
|
92
|
+
comparisonJoin?: boolean;
|
|
93
|
+
windowTotals?: boolean;
|
|
94
|
+
}
|
|
95
|
+
interface LogicalDimensionFilter {
|
|
96
|
+
dimension: Dimension;
|
|
97
|
+
operator: FilterOperator;
|
|
98
|
+
expression: string;
|
|
99
|
+
expression2?: string;
|
|
100
|
+
}
|
|
101
|
+
interface LogicalMetricFilter {
|
|
102
|
+
metric: Metric;
|
|
103
|
+
operator: MetricOperator;
|
|
104
|
+
expression: number;
|
|
105
|
+
expression2?: number;
|
|
106
|
+
}
|
|
107
|
+
interface LogicalFilterLeaf {
|
|
108
|
+
kind: 'leaf';
|
|
109
|
+
filter: LogicalDimensionFilter;
|
|
110
|
+
}
|
|
111
|
+
interface LogicalFilterGroup {
|
|
112
|
+
kind: 'group';
|
|
113
|
+
groupType: 'and' | 'or';
|
|
114
|
+
children: LogicalFilterNode[];
|
|
115
|
+
}
|
|
116
|
+
type LogicalFilterNode = LogicalFilterLeaf | LogicalFilterGroup;
|
|
117
|
+
interface LogicalQueryPlan {
|
|
118
|
+
dataset: LogicalDataset;
|
|
119
|
+
dimensions: Dimension[];
|
|
120
|
+
groupByDimensions: Dimension[];
|
|
121
|
+
hasDate: boolean;
|
|
122
|
+
metrics: Metric[];
|
|
123
|
+
dateRange: {
|
|
124
|
+
startDate: string;
|
|
125
|
+
endDate: string;
|
|
126
|
+
};
|
|
127
|
+
dimensionFilters: LogicalDimensionFilter[];
|
|
128
|
+
dimensionFilterTree?: LogicalFilterNode;
|
|
129
|
+
metricFilters: LogicalMetricFilter[];
|
|
130
|
+
prefilters: LogicalMetricFilter[];
|
|
131
|
+
specialFilters: {
|
|
132
|
+
topLevel: boolean;
|
|
133
|
+
};
|
|
134
|
+
queryParams: Partial<Record<QueryParamName, string>>;
|
|
135
|
+
orderBy?: BuilderState['orderBy'];
|
|
136
|
+
rowLimit?: number;
|
|
137
|
+
startRow?: number;
|
|
138
|
+
}
|
|
139
|
+
interface LogicalComparisonPlan {
|
|
140
|
+
current: LogicalQueryPlan;
|
|
141
|
+
previous: LogicalQueryPlan;
|
|
142
|
+
comparisonFilter?: ComparisonFilter;
|
|
143
|
+
}
|
|
144
|
+
declare function inferDataset(dimensions: readonly Dimension[], filterDims?: readonly Dimension[]): LogicalDataset;
|
|
145
|
+
/**
|
|
146
|
+
* True when every grouped + filtered dimension fits inside one stored dataset,
|
|
147
|
+
* i.e. the query is answerable from stored Parquet/D1 tables without a live
|
|
148
|
+
* GSC call. `inferDataset` always returns *some* dataset; this predicate is
|
|
149
|
+
* how callers tell a genuine match from one that will fail at column-resolve
|
|
150
|
+
* time.
|
|
151
|
+
*/
|
|
152
|
+
declare function isDatasetResolvable(dimensions: readonly Dimension[], filterDims?: readonly Dimension[]): boolean;
|
|
153
|
+
/**
|
|
154
|
+
* `BuilderState`-level convenience for {@link isDatasetResolvable}: extracts
|
|
155
|
+
* the state's dimension filters (the same way `buildLogicalPlan` does) and
|
|
156
|
+
* checks them against the grouped dimensions. Lets routing code (e.g. the
|
|
157
|
+
* composite source) detect a cross-dimension query without rebuilding a plan.
|
|
158
|
+
*/
|
|
159
|
+
declare function isStateResolvable(state: BuilderState): boolean;
|
|
160
|
+
/**
|
|
161
|
+
* Errors-as-values core: builds the logical plan or returns a typed `QueryError`
|
|
162
|
+
* for every modelled failure (missing date range, a regex filter on an engine
|
|
163
|
+
* without regex pushdown, a cross-dimension query with no stored home).
|
|
164
|
+
* `buildLogicalPlan` is the throwing wrapper for call sites that prefer exceptions.
|
|
165
|
+
*/
|
|
166
|
+
declare function buildLogicalPlanResult(state: BuilderState, capabilities?: PlannerCapabilities): Result<LogicalQueryPlan, QueryError>;
|
|
167
|
+
declare function buildLogicalPlan(state: BuilderState, capabilities?: PlannerCapabilities): LogicalQueryPlan;
|
|
168
|
+
/**
|
|
169
|
+
* Errors-as-values core for the comparison plan: returns a typed `QueryError`
|
|
170
|
+
* when the engine lacks the comparison-join or multi-dataset capability the
|
|
171
|
+
* paired queries need, or when either side fails to plan.
|
|
172
|
+
*/
|
|
173
|
+
declare function buildLogicalComparisonPlanResult(current: BuilderState, previous: BuilderState, capabilities?: PlannerCapabilities, comparisonFilter?: ComparisonFilter): Result<LogicalComparisonPlan, QueryError>;
|
|
174
|
+
declare function buildLogicalComparisonPlan(current: BuilderState, previous: BuilderState, capabilities?: PlannerCapabilities, comparisonFilter?: ComparisonFilter): LogicalComparisonPlan;
|
|
175
|
+
export { ComparisonFilter, LogicalComparisonPlan, LogicalDataset, LogicalDimensionFilter, LogicalFilterGroup, LogicalFilterLeaf, LogicalFilterNode, LogicalMetricFilter, LogicalQueryPlan, PlannerCapabilities, QueryError, QueryErrorKind, UnresolvableDatasetError, UnsupportedLogicalCapabilityError, buildLogicalComparisonPlan, buildLogicalComparisonPlanResult, buildLogicalPlan, buildLogicalPlanResult, inferDataset, isDatasetResolvable, isQueryError, isStateResolvable, queryErrorToException, queryErrors };
|