@gscdump/engine-gsc-api 0.21.3 → 0.22.1
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/index.d.mts +50 -2
- package/dist/index.mjs +97 -9
- package/package.json +4 -4
package/dist/index.d.mts
CHANGED
|
@@ -82,14 +82,21 @@ interface SyncSliceDomainFilter {
|
|
|
82
82
|
*/
|
|
83
83
|
domain?: string;
|
|
84
84
|
}
|
|
85
|
+
interface SyncSliceDimensionFilter {
|
|
86
|
+
dimension: 'page' | 'query' | 'country' | 'device' | 'searchAppearance';
|
|
87
|
+
operator?: 'equals' | 'notEquals' | 'contains' | 'notContains' | 'includingRegex' | 'excludingRegex';
|
|
88
|
+
expression: string;
|
|
89
|
+
}
|
|
85
90
|
interface RunGscSyncSliceOptions {
|
|
86
91
|
client: GoogleSearchConsoleClient;
|
|
87
92
|
siteUrl: string;
|
|
88
93
|
/** One of the engine sync-fan tables. Drives the dimension list. */
|
|
89
|
-
table: 'pages' | 'queries' | 'countries' | 'dates' | 'page_queries' | 'hourly_pages';
|
|
94
|
+
table: 'pages' | 'queries' | 'countries' | 'dates' | 'page_queries' | 'search_appearance' | 'search_appearance_pages' | 'search_appearance_queries' | 'search_appearance_page_queries' | 'hourly_pages';
|
|
90
95
|
startDate: string;
|
|
91
96
|
endDate: string;
|
|
92
97
|
domainFilter?: SyncSliceDomainFilter | null;
|
|
98
|
+
/** Additional AND filters, e.g. `searchAppearance = AMP_BLUE_LINK`. */
|
|
99
|
+
dimensionFilters?: SyncSliceDimensionFilter[];
|
|
93
100
|
/**
|
|
94
101
|
* Override the dimension list for this slice. Defaults to
|
|
95
102
|
* `DIMENSIONS_BY_TABLE[table]`. Hosts that need bespoke groupings (e.g.
|
|
@@ -139,5 +146,46 @@ interface RunGscSyncSliceResult {
|
|
|
139
146
|
*/
|
|
140
147
|
metadata?: GscSearchAnalyticsMetadata;
|
|
141
148
|
}
|
|
149
|
+
type SearchAppearanceContextGrain = 'page' | 'query' | 'page_query';
|
|
150
|
+
type SearchAppearanceContextTable = 'search_appearance_pages' | 'search_appearance_queries' | 'search_appearance_page_queries';
|
|
151
|
+
interface RunGscSearchAppearanceContextSliceOptions {
|
|
152
|
+
client: GoogleSearchConsoleClient;
|
|
153
|
+
siteUrl: string;
|
|
154
|
+
startDate: string;
|
|
155
|
+
endDate: string;
|
|
156
|
+
domainFilter?: SyncSliceDomainFilter | null;
|
|
157
|
+
/** Use a known appearance list to skip discovery. */
|
|
158
|
+
appearances?: string[];
|
|
159
|
+
/** Context grain to fetch for every discovered appearance. Defaults to page_query. */
|
|
160
|
+
grain?: SearchAppearanceContextGrain;
|
|
161
|
+
/** Context table to fetch. Overrides `grain` when provided. */
|
|
162
|
+
table?: SearchAppearanceContextTable;
|
|
163
|
+
dataState?: GscDataState;
|
|
164
|
+
rowLimit?: number;
|
|
165
|
+
maxPages?: number;
|
|
166
|
+
cpuBudgetMs?: number;
|
|
167
|
+
searchType?: SearchType$1;
|
|
168
|
+
onTotalBatch?: (rows: GscApiRow[]) => Promise<void>;
|
|
169
|
+
onContextBatch: (batch: {
|
|
170
|
+
searchAppearance: string;
|
|
171
|
+
table: SearchAppearanceContextTable;
|
|
172
|
+
rows: GscApiRow[];
|
|
173
|
+
}) => Promise<void>;
|
|
174
|
+
onPage?: (info: {
|
|
175
|
+
searchType: SearchType$1;
|
|
176
|
+
rowsThisPage: number;
|
|
177
|
+
}) => void;
|
|
178
|
+
}
|
|
179
|
+
interface RunGscSearchAppearanceContextSliceResult {
|
|
180
|
+
appearances: string[];
|
|
181
|
+
totalRows: number;
|
|
182
|
+
hasMore: boolean;
|
|
183
|
+
}
|
|
142
184
|
declare function runGscSyncSlice(opts: RunGscSyncSliceOptions): Promise<RunGscSyncSliceResult>;
|
|
143
|
-
|
|
185
|
+
/**
|
|
186
|
+
* Implements GSC's required two-step search-appearance flow:
|
|
187
|
+
* 1. group by `searchAppearance` alone to discover available appearances;
|
|
188
|
+
* 2. for each appearance, filter by it and fetch page/query/date context.
|
|
189
|
+
*/
|
|
190
|
+
declare function runGscSearchAppearanceContextSlice(opts: RunGscSearchAppearanceContextSliceOptions): Promise<RunGscSearchAppearanceContextSliceResult>;
|
|
191
|
+
export { type CreateLiveGscSourceOptions, type FetchTopNOptions, type GscApiQuerySourceOptions, type GscApiRow, type GscDailyRow, type GscRange, type GscTopNRow, type RunGscSearchAppearanceContextSliceOptions, type RunGscSearchAppearanceContextSliceResult, type RunGscSyncSliceOptions, type RunGscSyncSliceResult, type SearchAppearanceContextGrain, type SearchAppearanceContextTable, type SyncSliceDimensionFilter, type SyncSliceDomainFilter, canProxyToGsc, createGscApiQuerySource, createLiveGscSource, fetchGscDaily, fetchGscTopN, runGscSearchAppearanceContextSlice, runGscSyncSlice };
|
package/dist/index.mjs
CHANGED
|
@@ -172,19 +172,35 @@ const DIMENSIONS_BY_TABLE = {
|
|
|
172
172
|
"query",
|
|
173
173
|
"date"
|
|
174
174
|
],
|
|
175
|
+
search_appearance: ["searchAppearance"],
|
|
176
|
+
search_appearance_pages: ["page", "date"],
|
|
177
|
+
search_appearance_queries: ["query", "date"],
|
|
178
|
+
search_appearance_page_queries: [
|
|
179
|
+
"page",
|
|
180
|
+
"query",
|
|
181
|
+
"date"
|
|
182
|
+
],
|
|
175
183
|
hourly_pages: ["hour", "page"]
|
|
176
184
|
};
|
|
177
185
|
function isTimeoutLike(err) {
|
|
178
186
|
if (!(err instanceof Error)) return false;
|
|
179
187
|
return err.name === "AbortError" || err.message?.includes("timeout") || err.message?.includes("aborted");
|
|
180
188
|
}
|
|
181
|
-
function
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
189
|
+
function buildDimensionFilterGroups(domainFilter, filters = []) {
|
|
190
|
+
const out = filters.map((f) => ({
|
|
191
|
+
dimension: f.dimension,
|
|
192
|
+
operator: f.operator ?? "equals",
|
|
193
|
+
expression: f.expression
|
|
194
|
+
}));
|
|
195
|
+
if (domainFilter?.domain) {
|
|
196
|
+
const pattern = `^https?://(www\\.)?${domainFilter.domain.replace(/^www\./, "").replace(/\./g, "\\.")}/`;
|
|
197
|
+
out.push({
|
|
198
|
+
dimension: "page",
|
|
199
|
+
operator: "includingRegex",
|
|
200
|
+
expression: pattern
|
|
201
|
+
});
|
|
202
|
+
}
|
|
203
|
+
return out.length > 0 ? [{ filters: out }] : void 0;
|
|
188
204
|
}
|
|
189
205
|
async function runGscSyncSlice(opts) {
|
|
190
206
|
const rowLimit = opts.rowLimit ?? 500;
|
|
@@ -193,7 +209,7 @@ async function runGscSyncSlice(opts) {
|
|
|
193
209
|
const searchType = opts.searchType ?? "web";
|
|
194
210
|
const dimensions = opts.dimensions ? [...opts.dimensions] : [...DIMENSIONS_BY_TABLE[opts.table]];
|
|
195
211
|
const dataState = opts.dataState ?? "all";
|
|
196
|
-
const dimensionFilterGroups =
|
|
212
|
+
const dimensionFilterGroups = buildDimensionFilterGroups(opts.domainFilter, opts.dimensionFilters);
|
|
197
213
|
const loopStart = Date.now();
|
|
198
214
|
let startRow = opts.initialStartRow ?? 0;
|
|
199
215
|
let totalRows = 0;
|
|
@@ -261,4 +277,76 @@ async function runGscSyncSlice(opts) {
|
|
|
261
277
|
metadata
|
|
262
278
|
};
|
|
263
279
|
}
|
|
264
|
-
|
|
280
|
+
function contextTableForGrain(grain) {
|
|
281
|
+
switch (grain) {
|
|
282
|
+
case "page": return "search_appearance_pages";
|
|
283
|
+
case "query": return "search_appearance_queries";
|
|
284
|
+
case "page_query": return "search_appearance_page_queries";
|
|
285
|
+
}
|
|
286
|
+
}
|
|
287
|
+
async function runGscSearchAppearanceContextSlice(opts) {
|
|
288
|
+
const table = opts.table ?? contextTableForGrain(opts.grain ?? "page_query");
|
|
289
|
+
const appearances = opts.appearances?.slice() ?? [];
|
|
290
|
+
let totalRows = 0;
|
|
291
|
+
let hasMore = false;
|
|
292
|
+
if (!opts.appearances) {
|
|
293
|
+
const discovered = /* @__PURE__ */ new Set();
|
|
294
|
+
const discovery = await runGscSyncSlice({
|
|
295
|
+
client: opts.client,
|
|
296
|
+
siteUrl: opts.siteUrl,
|
|
297
|
+
table: "search_appearance",
|
|
298
|
+
startDate: opts.startDate,
|
|
299
|
+
endDate: opts.endDate,
|
|
300
|
+
domainFilter: opts.domainFilter,
|
|
301
|
+
dataState: opts.dataState,
|
|
302
|
+
rowLimit: opts.rowLimit,
|
|
303
|
+
maxPages: opts.maxPages,
|
|
304
|
+
cpuBudgetMs: opts.cpuBudgetMs,
|
|
305
|
+
searchType: opts.searchType,
|
|
306
|
+
onPage: opts.onPage,
|
|
307
|
+
onBatch: async (rows) => {
|
|
308
|
+
for (const row of rows) {
|
|
309
|
+
const value = String(row.keys?.[0] ?? "");
|
|
310
|
+
if (value) discovered.add(value);
|
|
311
|
+
}
|
|
312
|
+
await opts.onTotalBatch?.(rows);
|
|
313
|
+
}
|
|
314
|
+
});
|
|
315
|
+
totalRows += discovery.totalRows;
|
|
316
|
+
hasMore ||= discovery.hasMore;
|
|
317
|
+
appearances.push(...discovered);
|
|
318
|
+
}
|
|
319
|
+
for (const searchAppearance of appearances) {
|
|
320
|
+
const context = await runGscSyncSlice({
|
|
321
|
+
client: opts.client,
|
|
322
|
+
siteUrl: opts.siteUrl,
|
|
323
|
+
table,
|
|
324
|
+
startDate: opts.startDate,
|
|
325
|
+
endDate: opts.endDate,
|
|
326
|
+
domainFilter: opts.domainFilter,
|
|
327
|
+
dimensionFilters: [{
|
|
328
|
+
dimension: "searchAppearance",
|
|
329
|
+
expression: searchAppearance
|
|
330
|
+
}],
|
|
331
|
+
dataState: opts.dataState,
|
|
332
|
+
rowLimit: opts.rowLimit,
|
|
333
|
+
maxPages: opts.maxPages,
|
|
334
|
+
cpuBudgetMs: opts.cpuBudgetMs,
|
|
335
|
+
searchType: opts.searchType,
|
|
336
|
+
onPage: opts.onPage,
|
|
337
|
+
onBatch: (rows) => opts.onContextBatch({
|
|
338
|
+
searchAppearance,
|
|
339
|
+
table,
|
|
340
|
+
rows
|
|
341
|
+
})
|
|
342
|
+
});
|
|
343
|
+
totalRows += context.totalRows;
|
|
344
|
+
hasMore ||= context.hasMore;
|
|
345
|
+
}
|
|
346
|
+
return {
|
|
347
|
+
appearances,
|
|
348
|
+
totalRows,
|
|
349
|
+
hasMore
|
|
350
|
+
};
|
|
351
|
+
}
|
|
352
|
+
export { canProxyToGsc, createGscApiQuerySource, createLiveGscSource, fetchGscDaily, fetchGscTopN, runGscSearchAppearanceContextSlice, runGscSyncSlice };
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@gscdump/engine-gsc-api",
|
|
3
3
|
"type": "module",
|
|
4
|
-
"version": "0.
|
|
4
|
+
"version": "0.22.1",
|
|
5
5
|
"description": "GSC live-API engine adapter — wraps the Search Analytics REST API as an AnalysisQuerySource for typed analyzer dispatch.",
|
|
6
6
|
"author": {
|
|
7
7
|
"name": "Harlan Wilton",
|
|
@@ -36,11 +36,11 @@
|
|
|
36
36
|
"node": ">=18"
|
|
37
37
|
},
|
|
38
38
|
"dependencies": {
|
|
39
|
-
"
|
|
40
|
-
"gscdump": "0.
|
|
39
|
+
"gscdump": "0.22.1",
|
|
40
|
+
"@gscdump/engine": "0.22.1"
|
|
41
41
|
},
|
|
42
42
|
"devDependencies": {
|
|
43
|
-
"vitest": "^4.1.
|
|
43
|
+
"vitest": "^4.1.7"
|
|
44
44
|
},
|
|
45
45
|
"scripts": {
|
|
46
46
|
"build": "obuild",
|