gscdump 0.1.2 → 0.2.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,45 +1,11 @@
1
- import "ofetch";
2
- import { indexing_v3 } from "@googleapis/indexing/v3";
1
+ import _dayjs, { Dayjs } from "dayjs";
3
2
  import { searchconsole_v1 } from "@googleapis/searchconsole/v1";
4
3
 
5
4
  //#region src/core/types.d.ts
6
- type ApiSite = searchconsole_v1.Schema$WmxSite;
7
- type ApiSitemap = searchconsole_v1.Schema$WmxSitemap;
8
- type SearchAnalyticsQuery = searchconsole_v1.Schema$SearchAnalyticsQueryRequest;
9
- type SearchAnalyticsResponse = searchconsole_v1.Schema$SearchAnalyticsQueryResponse;
10
- type InspectUrlIndexResponse = searchconsole_v1.Schema$InspectUrlIndexResponse;
11
- type UrlNotificationMetadata = indexing_v3.Schema$UrlNotificationMetadata;
12
- type PublishUrlNotificationResponse = indexing_v3.Schema$PublishUrlNotificationResponse;
13
- //#endregion
14
- //#region src/core/client.d.ts
15
5
 
16
- interface GoogleSearchConsoleClient {
17
- sites: {
18
- list: () => Promise<{
19
- siteEntry?: ApiSite[];
20
- }>;
21
- };
22
- sitemaps: {
23
- list: (siteUrl: string) => Promise<{
24
- sitemap?: ApiSitemap[];
25
- }>;
26
- get: (siteUrl: string, feedpath: string) => Promise<ApiSitemap>;
27
- submit: (siteUrl: string, feedpath: string) => Promise<void>;
28
- delete: (siteUrl: string, feedpath: string) => Promise<void>;
29
- };
30
- searchAnalytics: {
31
- query: (siteUrl: string, body: SearchAnalyticsQuery) => Promise<SearchAnalyticsResponse>;
32
- };
33
- urlInspection: {
34
- inspect: (siteUrl: string, inspectionUrl: string) => Promise<InspectUrlIndexResponse>;
35
- };
36
- indexing: {
37
- publish: (url: string, type: 'URL_UPDATED' | 'URL_DELETED') => Promise<PublishUrlNotificationResponse>;
38
- getMetadata: (url: string) => Promise<UrlNotificationMetadata>;
39
- };
40
- }
6
+ type SearchAnalyticsQuery = searchconsole_v1.Schema$SearchAnalyticsQueryRequest;
41
7
  //#endregion
42
- //#region src/utils/countries.d.ts
8
+ //#region src/query/utils/countries.d.ts
43
9
  declare const _default: {
44
10
  name: string;
45
11
  'alpha-2': string;
@@ -48,14 +14,21 @@ declare const _default: {
48
14
  }[];
49
15
  //#endregion
50
16
  //#region src/query/constants.d.ts
51
- declare const Device: {
17
+ declare const Devices: {
52
18
  readonly MOBILE: "MOBILE";
53
19
  readonly DESKTOP: "DESKTOP";
54
20
  readonly TABLET: "TABLET";
55
21
  };
56
- type Device = typeof Device[keyof typeof Device];
57
- declare const Country: { [K in (typeof _default)[number]["alpha-3"]]: Lowercase<K> };
58
- type Country = typeof Country[keyof typeof Country];
22
+ type Device = typeof Devices[keyof typeof Devices];
23
+ declare const SearchTypes: {
24
+ readonly WEB: "web";
25
+ readonly IMAGE: "image";
26
+ readonly VIDEO: "video";
27
+ readonly NEWS: "news";
28
+ };
29
+ type SearchType = typeof SearchTypes[keyof typeof SearchTypes];
30
+ declare const Countries: { [K in (typeof _default)[number]["alpha-3"]]: Lowercase<K> };
31
+ type Country = typeof Countries[keyof typeof Countries];
59
32
  //#endregion
60
33
  //#region src/query/types.d.ts
61
34
  interface DimensionValueMap {
@@ -67,15 +40,24 @@ interface DimensionValueMap {
67
40
  date: string;
68
41
  }
69
42
  type Dimension = keyof DimensionValueMap;
43
+ interface QueryParamValueMap {
44
+ searchType: SearchType;
45
+ }
46
+ type QueryParamName = keyof QueryParamValueMap;
70
47
  declare const ColumnBrand: unique symbol;
71
48
  interface Column<D extends Dimension> {
72
49
  readonly [ColumnBrand]: D;
73
50
  readonly dimension: D;
74
51
  }
52
+ declare const QueryParamBrand: unique symbol;
53
+ interface QueryParam<P extends QueryParamName> {
54
+ readonly [QueryParamBrand]: P;
55
+ readonly param: P;
56
+ }
75
57
  type FilterOperator = 'equals' | 'notEquals' | 'contains' | 'notContains' | 'includingRegex' | 'excludingRegex';
76
58
  type DateOperator = 'gte' | 'gt' | 'lte' | 'lt' | 'between';
77
59
  interface InternalFilter {
78
- dimension: Dimension;
60
+ dimension: Dimension | QueryParamName;
79
61
  operator: FilterOperator | DateOperator;
80
62
  expression: string;
81
63
  expression2?: string;
@@ -85,6 +67,7 @@ interface Filter<C = object> {
85
67
  readonly [FilterBrand]: true;
86
68
  readonly _constraints: C;
87
69
  readonly _filters: InternalFilter[];
70
+ readonly _nestedGroups?: Filter<any>[];
88
71
  readonly _groupType?: 'and' | 'or';
89
72
  }
90
73
  type MergeConstraints<F extends Filter<any>[]> = UnionToIntersection<F[number]['_constraints']>;
@@ -100,8 +83,7 @@ type GSCRow<D extends Dimension[], C> = { [K in D[number]]: K extends keyof C ?
100
83
  };
101
84
  interface BuilderState {
102
85
  dimensions: Dimension[];
103
- filters: Filter<any>[];
104
- siteUrl?: string;
86
+ filter?: Filter<any>;
105
87
  rowLimit?: number;
106
88
  startRow?: number;
107
89
  }
@@ -110,12 +92,9 @@ interface BuilderState {
110
92
  interface GSCQueryBuilder<D extends Dimension[] = [], C = object> {
111
93
  select: <T extends Dimension[]>(...dims: T) => GSCQueryBuilder<T, C>;
112
94
  where: <F extends Filter<any>>(filter: F) => GSCQueryBuilder<D, C & F['_constraints']>;
113
- siteUrl: (url: string) => GSCQueryBuilder<D, C>;
114
95
  limit: (n: number) => GSCQueryBuilder<D, C>;
115
96
  offset: (n: number) => GSCQueryBuilder<D, C>;
116
- execute: (client: GoogleSearchConsoleClient) => Promise<GSCResult<D, C>>;
117
97
  toBody: () => SearchAnalyticsQuery;
118
- /** Expose internal state for analysis functions to merge with */
119
98
  getState: () => BuilderState;
120
99
  }
121
100
  declare const gsc: GSCQueryBuilder<[], object>;
@@ -127,9 +106,11 @@ declare const device: Column<"device">;
127
106
  declare const country: Column<"country">;
128
107
  declare const searchAppearance: Column<"searchAppearance">;
129
108
  declare const date: Column<"date">;
109
+ declare const searchType: QueryParam<"searchType">;
130
110
  //#endregion
131
111
  //#region src/query/operators.d.ts
132
112
  declare function eq<D extends Dimension, V extends DimensionValueMap[D]>(column: Column<D>, value: V): Filter<Record<D, V>>;
113
+ declare function eq<P extends QueryParamName, V extends QueryParamValueMap[P]>(param: QueryParam<P>, value: V): Filter<Record<P, V>>;
133
114
  declare function ne<D extends Dimension>(column: Column<D>, value: DimensionValueMap[D]): Filter<object>;
134
115
  declare function inArray<D extends Dimension, V extends DimensionValueMap[D]>(column: Column<D>, values: readonly V[]): Filter<Record<D, V>>;
135
116
  declare function contains<D extends Dimension>(column: Column<D>, pattern: string): Filter<object>;
@@ -146,12 +127,18 @@ declare function lt<D extends Dimension>(column: Column<D>, value: DimensionValu
146
127
  declare function between<D extends Dimension>(column: Column<D>, start: DimensionValueMap[D], end: DimensionValueMap[D]): Filter<object>;
147
128
  //#endregion
148
129
  //#region src/query/resolver.d.ts
149
- /**
150
- * Extract date range from filters. Used by analysis functions to get the period.
151
- */
152
- declare function extractDateRange(filters: Filter<any>[]): {
130
+ declare function extractDateRange(filter?: Filter<any>): {
153
131
  startDate?: string;
154
132
  endDate?: string;
155
133
  };
156
134
  //#endregion
157
- export { type BuilderState, type Column, Country, type Country as CountryType, Device, type Device as DeviceType, type Dimension, type DimensionValueMap, type Filter, type GSCQueryBuilder, type GSCResult, type GSCRow, and, between, contains, country, date, device, eq, extractDateRange, gsc, gt, gte, inArray, like, lt, lte, ne, not, notRegex, or, page, query, regex, searchAppearance };
135
+ //#region src/query/utils/dayjs.d.ts
136
+ declare function dayjs(date?: _dayjs.ConfigType): Dayjs;
137
+ declare function currentPstDate(): string;
138
+ declare function dayjsPst(): Dayjs;
139
+ //#endregion
140
+ //#region src/query/index.d.ts
141
+ declare function today(): string;
142
+ declare function daysAgo(n: number): string;
143
+ //#endregion
144
+ export { type BuilderState, type Column, Countries, type Country, type Device, Devices, type Dimension, type DimensionValueMap, type Filter, type GSCQueryBuilder, type GSCResult, type GSCRow, type QueryParam, type QueryParamName, type QueryParamValueMap, type SearchType, SearchTypes, and, between, contains, country, currentPstDate, date, dayjs, dayjsPst, daysAgo, device, eq, extractDateRange, gsc, gt, gte, inArray, like, lt, lte, ne, not, notRegex, or, page, query, regex, searchAppearance, searchType, today };
@@ -1,3 +1,21 @@
1
+ import _dayjs from "dayjs";
2
+ import timezone from "dayjs/plugin/timezone.js";
3
+ import utc from "dayjs/plugin/utc.js";
4
+
5
+ //#region src/query/utils/dayjs.ts
6
+ _dayjs.extend(utc);
7
+ _dayjs.extend(timezone);
8
+ function dayjs(date$1) {
9
+ return _dayjs(date$1);
10
+ }
11
+ function currentPstDate() {
12
+ return dayjs().tz("America/Los_Angeles").hour(12).minute(0).second(0).format("YYYY-MM-DD");
13
+ }
14
+ function dayjsPst() {
15
+ return dayjs().tz("America/Los_Angeles").hour(12).minute(0).second(0);
16
+ }
17
+
18
+ //#endregion
1
19
  //#region src/query/resolver.ts
2
20
  const DATE_OPERATORS = [
3
21
  "gte",
@@ -6,110 +24,110 @@ const DATE_OPERATORS = [
6
24
  "lt",
7
25
  "between"
8
26
  ];
27
+ const QUERY_PARAMS = ["searchType"];
9
28
  function isDateOperator(op) {
10
29
  return DATE_OPERATORS.includes(op);
11
30
  }
31
+ function isQueryParam(dim) {
32
+ return QUERY_PARAMS.includes(dim);
33
+ }
12
34
  function addDays(dateStr, days) {
13
35
  const d = new Date(dateStr);
14
36
  d.setDate(d.getDate() + days);
15
37
  return d.toISOString().split("T")[0];
16
38
  }
17
- /**
18
- * Extract date range from filters. Used by analysis functions to get the period.
19
- */
20
- function extractDateRange(filters) {
21
- const { startDate, endDate } = extractDateFilters(filters);
22
- return {
23
- startDate,
24
- endDate
25
- };
26
- }
27
- function extractDateFilters(filters) {
39
+ function extractSpecialFilters(filter) {
40
+ if (!filter) return {};
28
41
  let startDate;
29
42
  let endDate;
30
- const nonDateFilters = [];
31
- for (const filter of filters) {
32
- const dateFilters = [];
33
- const otherFilters = [];
34
- for (const f of filter._filters) if (f.dimension === "date" && isDateOperator(f.operator)) dateFilters.push(f);
35
- else otherFilters.push(f);
36
- for (const df of dateFilters) switch (df.operator) {
37
- case "gte":
38
- startDate = df.expression;
39
- break;
40
- case "gt":
41
- startDate = addDays(df.expression, 1);
42
- break;
43
- case "lte":
44
- endDate = df.expression;
45
- break;
46
- case "lt":
47
- endDate = addDays(df.expression, -1);
48
- break;
49
- case "between":
50
- startDate = df.expression;
51
- endDate = df.expression2;
52
- break;
53
- }
54
- if (otherFilters.length > 0) nonDateFilters.push({
55
- ...filter,
56
- _filters: otherFilters
57
- });
43
+ let searchType$1;
44
+ const otherFilters = [];
45
+ const cleanedNestedGroups = [];
46
+ for (const f of filter._filters) if (f.dimension === "date" && isDateOperator(f.operator)) switch (f.operator) {
47
+ case "gte":
48
+ startDate = f.expression;
49
+ break;
50
+ case "gt":
51
+ startDate = addDays(f.expression, 1);
52
+ break;
53
+ case "lte":
54
+ endDate = f.expression;
55
+ break;
56
+ case "lt":
57
+ endDate = addDays(f.expression, -1);
58
+ break;
59
+ case "between":
60
+ startDate = f.expression;
61
+ endDate = f.expression2;
62
+ break;
63
+ }
64
+ else if (isQueryParam(f.dimension)) {
65
+ if (f.dimension === "searchType") searchType$1 = f.expression;
66
+ } else otherFilters.push(f);
67
+ if (filter._nestedGroups) for (const nested of filter._nestedGroups) {
68
+ const extracted = extractSpecialFilters(nested);
69
+ if (extracted.startDate) startDate = extracted.startDate;
70
+ if (extracted.endDate) endDate = extracted.endDate;
71
+ if (extracted.searchType) searchType$1 = extracted.searchType;
72
+ if (extracted.dimensionFilter) cleanedNestedGroups.push(extracted.dimensionFilter);
58
73
  }
74
+ const dimensionFilter = otherFilters.length > 0 || cleanedNestedGroups.length > 0 ? {
75
+ ...filter,
76
+ _filters: otherFilters,
77
+ _nestedGroups: cleanedNestedGroups.length > 0 ? cleanedNestedGroups : void 0
78
+ } : void 0;
59
79
  return {
60
80
  startDate,
61
81
  endDate,
62
- nonDateFilters
82
+ searchType: searchType$1,
83
+ dimensionFilter
84
+ };
85
+ }
86
+ function extractDateRange(filter) {
87
+ const { startDate, endDate } = extractSpecialFilters(filter);
88
+ return {
89
+ startDate,
90
+ endDate
63
91
  };
64
92
  }
65
93
  function resolveToBody(state) {
66
- const { startDate, endDate, nonDateFilters } = extractDateFilters(state.filters);
67
- if (!startDate || !endDate) throw new Error("Date range required: use .where(between(date, start, end)) or .where(gte(date, start)).where(lte(date, end))");
94
+ const { startDate, endDate, searchType: searchType$1, dimensionFilter } = extractSpecialFilters(state.filter);
95
+ if (!startDate || !endDate) throw new Error("Date range required: use .where(between(date, start, end)) or .where(and(gte(date, start), lte(date, end)))");
68
96
  const body = {
69
97
  dimensions: state.dimensions,
70
98
  startDate,
71
99
  endDate
72
100
  };
101
+ if (searchType$1) body.searchType = searchType$1;
73
102
  if (state.rowLimit) body.rowLimit = state.rowLimit;
74
103
  if (state.startRow) body.startRow = state.startRow;
75
- const filterGroups = resolveFilters(nonDateFilters);
104
+ const filterGroups = resolveFilter(dimensionFilter);
76
105
  if (filterGroups.length > 0) body.dimensionFilterGroups = filterGroups;
77
106
  return body;
78
107
  }
79
- function resolveFilters(filters) {
108
+ function resolveFilter(filter) {
109
+ if (!filter) return [];
80
110
  const groups = [];
81
- for (const filter of filters) if ((filter._groupType ?? "and") === "or" && filter._filters.length > 1) groups.push({
82
- groupType: "or",
83
- filters: filter._filters.map((f) => ({
84
- dimension: f.dimension,
85
- operator: f.operator,
86
- expression: f.expression
87
- }))
88
- });
89
- else if (filter._filters.length > 0) groups.push({ filters: filter._filters.map((f) => ({
111
+ if ((filter._groupType ?? "and") === "or") {
112
+ if (filter._filters.length > 0) groups.push({
113
+ groupType: "or",
114
+ filters: filter._filters.map((f) => ({
115
+ dimension: f.dimension,
116
+ operator: f.operator,
117
+ expression: f.expression
118
+ }))
119
+ });
120
+ } else if (filter._filters.length > 0) groups.push({ filters: filter._filters.map((f) => ({
90
121
  dimension: f.dimension,
91
122
  operator: f.operator,
92
123
  expression: f.expression
93
124
  })) });
125
+ if (filter._nestedGroups) for (const nested of filter._nestedGroups) groups.push(...resolveFilter(nested));
94
126
  return groups;
95
127
  }
96
128
 
97
129
  //#endregion
98
130
  //#region src/query/builder.ts
99
- function transformResponse(response, dimensions) {
100
- return { rows: (response.rows ?? []).map((row) => {
101
- const result = {
102
- clicks: row.clicks,
103
- impressions: row.impressions,
104
- ctr: row.ctr,
105
- position: row.position
106
- };
107
- dimensions.forEach((dim, i) => {
108
- result[dim] = row.keys?.[i];
109
- });
110
- return result;
111
- }) };
112
- }
113
131
  function createBuilder(state) {
114
132
  return {
115
133
  select(...dims) {
@@ -121,13 +139,7 @@ function createBuilder(state) {
121
139
  where(filter) {
122
140
  return createBuilder({
123
141
  ...state,
124
- filters: [...state.filters, filter]
125
- });
126
- },
127
- siteUrl(url) {
128
- return createBuilder({
129
- ...state,
130
- siteUrl: url
142
+ filter
131
143
  });
132
144
  },
133
145
  limit(n) {
@@ -142,10 +154,6 @@ function createBuilder(state) {
142
154
  startRow: n
143
155
  });
144
156
  },
145
- async execute(client) {
146
- const body = resolveToBody(state);
147
- return transformResponse(await client.searchAnalytics.query(state.siteUrl, body), state.dimensions);
148
- },
149
157
  toBody() {
150
158
  return resolveToBody(state);
151
159
  },
@@ -154,25 +162,26 @@ function createBuilder(state) {
154
162
  }
155
163
  };
156
164
  }
157
- const gsc = createBuilder({
158
- dimensions: [],
159
- filters: []
160
- });
165
+ const gsc = createBuilder({ dimensions: [] });
161
166
 
162
167
  //#endregion
163
168
  //#region src/query/columns.ts
164
169
  function createColumn(dimension) {
165
170
  return { dimension };
166
171
  }
172
+ function createQueryParam(param) {
173
+ return { param };
174
+ }
167
175
  const page = createColumn("page");
168
176
  const query = createColumn("query");
169
177
  const device = createColumn("device");
170
178
  const country = createColumn("country");
171
179
  const searchAppearance = createColumn("searchAppearance");
172
180
  const date = createColumn("date");
181
+ const searchType = createQueryParam("searchType");
173
182
 
174
183
  //#endregion
175
- //#region src/utils/countries.ts
184
+ //#region src/query/utils/countries.ts
176
185
  var countries_default = [
177
186
  {
178
187
  "name": "Afghanistan",
@@ -1672,20 +1681,26 @@ var countries_default = [
1672
1681
 
1673
1682
  //#endregion
1674
1683
  //#region src/query/constants.ts
1675
- const Device = {
1684
+ const Devices = {
1676
1685
  MOBILE: "MOBILE",
1677
1686
  DESKTOP: "DESKTOP",
1678
1687
  TABLET: "TABLET"
1679
1688
  };
1680
- const Country = Object.fromEntries(countries_default.map((c) => [c["alpha-3"], c["alpha-3"].toLowerCase()]));
1689
+ const SearchTypes = {
1690
+ WEB: "web",
1691
+ IMAGE: "image",
1692
+ VIDEO: "video",
1693
+ NEWS: "news"
1694
+ };
1695
+ const Countries = Object.fromEntries(countries_default.map((c) => [c["alpha-3"], c["alpha-3"].toLowerCase()]));
1681
1696
 
1682
1697
  //#endregion
1683
1698
  //#region src/query/operators.ts
1684
- function eq(column, value) {
1699
+ function eq(columnOrParam, value) {
1685
1700
  return {
1686
1701
  _constraints: {},
1687
1702
  _filters: [{
1688
- dimension: column.dimension,
1703
+ dimension: "dimension" in columnOrParam ? columnOrParam.dimension : columnOrParam.param,
1689
1704
  operator: "equals",
1690
1705
  expression: String(value)
1691
1706
  }]
@@ -1753,9 +1768,17 @@ function notRegex(column, pattern) {
1753
1768
  };
1754
1769
  }
1755
1770
  function and(...filters) {
1771
+ const flatFilters = [];
1772
+ const nestedGroups = [];
1773
+ for (const f of filters) if (f._groupType === "or") nestedGroups.push(f);
1774
+ else {
1775
+ flatFilters.push(...f._filters);
1776
+ if (f._nestedGroups) nestedGroups.push(...f._nestedGroups);
1777
+ }
1756
1778
  return {
1757
1779
  _constraints: {},
1758
- _filters: filters.flatMap((f) => f._filters),
1780
+ _filters: flatFilters,
1781
+ _nestedGroups: nestedGroups.length > 0 ? nestedGroups : void 0,
1759
1782
  _groupType: "and"
1760
1783
  };
1761
1784
  }
@@ -1845,4 +1868,13 @@ function between(column, start, end) {
1845
1868
  }
1846
1869
 
1847
1870
  //#endregion
1848
- export { Country, Device, and, between, contains, country, date, device, eq, extractDateRange, gsc, gt, gte, inArray, like, lt, lte, ne, not, notRegex, or, page, query, regex, searchAppearance };
1871
+ //#region src/query/index.ts
1872
+ function today() {
1873
+ return currentPstDate();
1874
+ }
1875
+ function daysAgo(n) {
1876
+ return dayjsPst().subtract(n, "day").format("YYYY-MM-DD");
1877
+ }
1878
+
1879
+ //#endregion
1880
+ export { Countries, Devices, SearchTypes, and, between, contains, country, currentPstDate, date, dayjs, dayjsPst, daysAgo, device, eq, extractDateRange, gsc, gt, gte, inArray, like, lt, lte, ne, not, notRegex, or, page, query, regex, searchAppearance, searchType, today };
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "gscdump",
3
3
  "type": "module",
4
- "version": "0.1.2",
4
+ "version": "0.2.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",
@@ -39,6 +39,10 @@
39
39
  "./query": {
40
40
  "types": "./dist/query/index.d.mts",
41
41
  "import": "./dist/query/index.mjs"
42
+ },
43
+ "./analysis": {
44
+ "types": "./dist/analysis/index.d.mts",
45
+ "import": "./dist/analysis/index.mjs"
42
46
  }
43
47
  },
44
48
  "main": "./dist/index.mjs",