gscdump 0.1.3 → 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,16 +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
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
157
140
  //#region src/query/index.d.ts
158
141
  declare function today(): string;
159
142
  declare function daysAgo(n: number): string;
160
143
  //#endregion
161
- 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, daysAgo, device, eq, extractDateRange, gsc, gt, gte, inArray, like, lt, lte, ne, not, notRegex, or, page, query, regex, searchAppearance, today };
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 };
@@ -2,7 +2,7 @@ import _dayjs from "dayjs";
2
2
  import timezone from "dayjs/plugin/timezone.js";
3
3
  import utc from "dayjs/plugin/utc.js";
4
4
 
5
- //#region src/utils/dayjs.ts
5
+ //#region src/query/utils/dayjs.ts
6
6
  _dayjs.extend(utc);
7
7
  _dayjs.extend(timezone);
8
8
  function dayjs(date$1) {
@@ -24,110 +24,110 @@ const DATE_OPERATORS = [
24
24
  "lt",
25
25
  "between"
26
26
  ];
27
+ const QUERY_PARAMS = ["searchType"];
27
28
  function isDateOperator(op) {
28
29
  return DATE_OPERATORS.includes(op);
29
30
  }
31
+ function isQueryParam(dim) {
32
+ return QUERY_PARAMS.includes(dim);
33
+ }
30
34
  function addDays(dateStr, days) {
31
35
  const d = new Date(dateStr);
32
36
  d.setDate(d.getDate() + days);
33
37
  return d.toISOString().split("T")[0];
34
38
  }
35
- /**
36
- * Extract date range from filters. Used by analysis functions to get the period.
37
- */
38
- function extractDateRange(filters) {
39
- const { startDate, endDate } = extractDateFilters(filters);
40
- return {
41
- startDate,
42
- endDate
43
- };
44
- }
45
- function extractDateFilters(filters) {
39
+ function extractSpecialFilters(filter) {
40
+ if (!filter) return {};
46
41
  let startDate;
47
42
  let endDate;
48
- const nonDateFilters = [];
49
- for (const filter of filters) {
50
- const dateFilters = [];
51
- const otherFilters = [];
52
- for (const f of filter._filters) if (f.dimension === "date" && isDateOperator(f.operator)) dateFilters.push(f);
53
- else otherFilters.push(f);
54
- for (const df of dateFilters) switch (df.operator) {
55
- case "gte":
56
- startDate = df.expression;
57
- break;
58
- case "gt":
59
- startDate = addDays(df.expression, 1);
60
- break;
61
- case "lte":
62
- endDate = df.expression;
63
- break;
64
- case "lt":
65
- endDate = addDays(df.expression, -1);
66
- break;
67
- case "between":
68
- startDate = df.expression;
69
- endDate = df.expression2;
70
- break;
71
- }
72
- if (otherFilters.length > 0) nonDateFilters.push({
73
- ...filter,
74
- _filters: otherFilters
75
- });
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);
76
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;
77
79
  return {
78
80
  startDate,
79
81
  endDate,
80
- 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
81
91
  };
82
92
  }
83
93
  function resolveToBody(state) {
84
- const { startDate, endDate, nonDateFilters } = extractDateFilters(state.filters);
85
- 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)))");
86
96
  const body = {
87
97
  dimensions: state.dimensions,
88
98
  startDate,
89
99
  endDate
90
100
  };
101
+ if (searchType$1) body.searchType = searchType$1;
91
102
  if (state.rowLimit) body.rowLimit = state.rowLimit;
92
103
  if (state.startRow) body.startRow = state.startRow;
93
- const filterGroups = resolveFilters(nonDateFilters);
104
+ const filterGroups = resolveFilter(dimensionFilter);
94
105
  if (filterGroups.length > 0) body.dimensionFilterGroups = filterGroups;
95
106
  return body;
96
107
  }
97
- function resolveFilters(filters) {
108
+ function resolveFilter(filter) {
109
+ if (!filter) return [];
98
110
  const groups = [];
99
- for (const filter of filters) if ((filter._groupType ?? "and") === "or" && filter._filters.length > 1) groups.push({
100
- groupType: "or",
101
- filters: filter._filters.map((f) => ({
102
- dimension: f.dimension,
103
- operator: f.operator,
104
- expression: f.expression
105
- }))
106
- });
107
- 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) => ({
108
121
  dimension: f.dimension,
109
122
  operator: f.operator,
110
123
  expression: f.expression
111
124
  })) });
125
+ if (filter._nestedGroups) for (const nested of filter._nestedGroups) groups.push(...resolveFilter(nested));
112
126
  return groups;
113
127
  }
114
128
 
115
129
  //#endregion
116
130
  //#region src/query/builder.ts
117
- function transformResponse(response, dimensions) {
118
- return { rows: (response.rows ?? []).map((row) => {
119
- const result = {
120
- clicks: row.clicks,
121
- impressions: row.impressions,
122
- ctr: row.ctr,
123
- position: row.position
124
- };
125
- dimensions.forEach((dim, i) => {
126
- result[dim] = row.keys?.[i];
127
- });
128
- return result;
129
- }) };
130
- }
131
131
  function createBuilder(state) {
132
132
  return {
133
133
  select(...dims) {
@@ -139,13 +139,7 @@ function createBuilder(state) {
139
139
  where(filter) {
140
140
  return createBuilder({
141
141
  ...state,
142
- filters: [...state.filters, filter]
143
- });
144
- },
145
- siteUrl(url) {
146
- return createBuilder({
147
- ...state,
148
- siteUrl: url
142
+ filter
149
143
  });
150
144
  },
151
145
  limit(n) {
@@ -160,10 +154,6 @@ function createBuilder(state) {
160
154
  startRow: n
161
155
  });
162
156
  },
163
- async execute(client) {
164
- const body = resolveToBody(state);
165
- return transformResponse(await client.searchAnalytics.query(state.siteUrl, body), state.dimensions);
166
- },
167
157
  toBody() {
168
158
  return resolveToBody(state);
169
159
  },
@@ -172,25 +162,26 @@ function createBuilder(state) {
172
162
  }
173
163
  };
174
164
  }
175
- const gsc = createBuilder({
176
- dimensions: [],
177
- filters: []
178
- });
165
+ const gsc = createBuilder({ dimensions: [] });
179
166
 
180
167
  //#endregion
181
168
  //#region src/query/columns.ts
182
169
  function createColumn(dimension) {
183
170
  return { dimension };
184
171
  }
172
+ function createQueryParam(param) {
173
+ return { param };
174
+ }
185
175
  const page = createColumn("page");
186
176
  const query = createColumn("query");
187
177
  const device = createColumn("device");
188
178
  const country = createColumn("country");
189
179
  const searchAppearance = createColumn("searchAppearance");
190
180
  const date = createColumn("date");
181
+ const searchType = createQueryParam("searchType");
191
182
 
192
183
  //#endregion
193
- //#region src/utils/countries.ts
184
+ //#region src/query/utils/countries.ts
194
185
  var countries_default = [
195
186
  {
196
187
  "name": "Afghanistan",
@@ -1690,20 +1681,26 @@ var countries_default = [
1690
1681
 
1691
1682
  //#endregion
1692
1683
  //#region src/query/constants.ts
1693
- const Device = {
1684
+ const Devices = {
1694
1685
  MOBILE: "MOBILE",
1695
1686
  DESKTOP: "DESKTOP",
1696
1687
  TABLET: "TABLET"
1697
1688
  };
1698
- 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()]));
1699
1696
 
1700
1697
  //#endregion
1701
1698
  //#region src/query/operators.ts
1702
- function eq(column, value) {
1699
+ function eq(columnOrParam, value) {
1703
1700
  return {
1704
1701
  _constraints: {},
1705
1702
  _filters: [{
1706
- dimension: column.dimension,
1703
+ dimension: "dimension" in columnOrParam ? columnOrParam.dimension : columnOrParam.param,
1707
1704
  operator: "equals",
1708
1705
  expression: String(value)
1709
1706
  }]
@@ -1771,9 +1768,17 @@ function notRegex(column, pattern) {
1771
1768
  };
1772
1769
  }
1773
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
+ }
1774
1778
  return {
1775
1779
  _constraints: {},
1776
- _filters: filters.flatMap((f) => f._filters),
1780
+ _filters: flatFilters,
1781
+ _nestedGroups: nestedGroups.length > 0 ? nestedGroups : void 0,
1777
1782
  _groupType: "and"
1778
1783
  };
1779
1784
  }
@@ -1872,4 +1877,4 @@ function daysAgo(n) {
1872
1877
  }
1873
1878
 
1874
1879
  //#endregion
1875
- export { Country, Device, and, between, contains, country, date, daysAgo, device, eq, extractDateRange, gsc, gt, gte, inArray, like, lt, lte, ne, not, notRegex, or, page, query, regex, searchAppearance, today };
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.3",
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",