@gscdump/engine-gsc-api 0.17.3 → 0.17.4
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 +55 -1
- package/dist/index.mjs +85 -1
- package/package.json +3 -3
package/dist/index.d.mts
CHANGED
|
@@ -64,4 +64,58 @@ interface GscApiQuerySourceOptions {
|
|
|
64
64
|
siteUrl: string;
|
|
65
65
|
}
|
|
66
66
|
declare function createGscApiQuerySource(options: GscApiQuerySourceOptions): AnalysisQuerySource;
|
|
67
|
-
|
|
67
|
+
interface GscApiRow {
|
|
68
|
+
keys: string[];
|
|
69
|
+
clicks: number;
|
|
70
|
+
impressions: number;
|
|
71
|
+
ctr: number;
|
|
72
|
+
position: number;
|
|
73
|
+
}
|
|
74
|
+
interface SyncSliceDomainFilter {
|
|
75
|
+
/**
|
|
76
|
+
* Domain (eTLD+1 + subdomain) to scope the slice to — matches both
|
|
77
|
+
* `www.` and bare variants. Strip the protocol; the regex is built here.
|
|
78
|
+
*/
|
|
79
|
+
domain?: string;
|
|
80
|
+
}
|
|
81
|
+
interface RunGscSyncSliceOptions {
|
|
82
|
+
client: GoogleSearchConsoleClient;
|
|
83
|
+
siteUrl: string;
|
|
84
|
+
/** One of the engine sync-fan tables. Drives the dimension list. */
|
|
85
|
+
table: 'pages' | 'keywords' | 'countries' | 'devices' | 'page_keywords';
|
|
86
|
+
startDate: string;
|
|
87
|
+
endDate: string;
|
|
88
|
+
domainFilter?: SyncSliceDomainFilter | null;
|
|
89
|
+
/**
|
|
90
|
+
* Invoked per GSC API page with the rows fetched. Return a promise; the
|
|
91
|
+
* loop awaits it before paging further. Throw inside to abort; AbortError /
|
|
92
|
+
* timeout messages are swallowed so the continuation can re-process.
|
|
93
|
+
*/
|
|
94
|
+
onBatch: (rows: GscApiRow[]) => Promise<void>;
|
|
95
|
+
initialStartRow?: number;
|
|
96
|
+
/**
|
|
97
|
+
* Max GSC API pages per call. `Infinity` for unbounded; hosts cap this
|
|
98
|
+
* based on path (D1 vs R2) and table.
|
|
99
|
+
*/
|
|
100
|
+
maxPages?: number;
|
|
101
|
+
/** GSC page size. 500 is the D1-safe default; 10k is the R2 path. */
|
|
102
|
+
rowLimit?: number;
|
|
103
|
+
/**
|
|
104
|
+
* Soft CPU budget for the loop itself. Returns `hasMore` when crossed so
|
|
105
|
+
* the continuation resumes from `nextStartRow`.
|
|
106
|
+
*/
|
|
107
|
+
cpuBudgetMs?: number;
|
|
108
|
+
searchType?: SearchType;
|
|
109
|
+
/** Invoked once per successful GSC API page. Hosts wire telemetry here. */
|
|
110
|
+
onPage?: (info: {
|
|
111
|
+
searchType: SearchType;
|
|
112
|
+
rowsThisPage: number;
|
|
113
|
+
}) => void;
|
|
114
|
+
}
|
|
115
|
+
interface RunGscSyncSliceResult {
|
|
116
|
+
totalRows: number;
|
|
117
|
+
hasMore: boolean;
|
|
118
|
+
nextStartRow: number;
|
|
119
|
+
}
|
|
120
|
+
declare function runGscSyncSlice(opts: RunGscSyncSliceOptions): Promise<RunGscSyncSliceResult>;
|
|
121
|
+
export { type CreateLiveGscSourceOptions, type FetchTopNOptions, type GscApiQuerySourceOptions, type GscApiRow, type GscDailyRow, type GscRange, type GscTopNRow, type RunGscSyncSliceOptions, type RunGscSyncSliceResult, type SyncSliceDomainFilter, canProxyToGsc, createGscApiQuerySource, createLiveGscSource, fetchGscDaily, fetchGscTopN, runGscSyncSlice };
|
package/dist/index.mjs
CHANGED
|
@@ -159,4 +159,88 @@ function createLiveGscSource(opts) {
|
|
|
159
159
|
}
|
|
160
160
|
};
|
|
161
161
|
}
|
|
162
|
-
|
|
162
|
+
const DIMENSIONS_BY_TABLE = {
|
|
163
|
+
pages: ["page", "date"],
|
|
164
|
+
keywords: ["query", "date"],
|
|
165
|
+
countries: ["country", "date"],
|
|
166
|
+
devices: ["device", "date"],
|
|
167
|
+
page_keywords: [
|
|
168
|
+
"page",
|
|
169
|
+
"query",
|
|
170
|
+
"date"
|
|
171
|
+
]
|
|
172
|
+
};
|
|
173
|
+
function isTimeoutLike(err) {
|
|
174
|
+
if (!(err instanceof Error)) return false;
|
|
175
|
+
return err.name === "AbortError" || err.message?.includes("timeout") || err.message?.includes("aborted");
|
|
176
|
+
}
|
|
177
|
+
function buildDomainFilterGroups(filter) {
|
|
178
|
+
if (!filter?.domain) return void 0;
|
|
179
|
+
return [{ filters: [{
|
|
180
|
+
dimension: "page",
|
|
181
|
+
operator: "includingRegex",
|
|
182
|
+
expression: `^https?://(www\\.)?${filter.domain.replace(/^www\./, "").replace(/\./g, "\\.")}/`
|
|
183
|
+
}] }];
|
|
184
|
+
}
|
|
185
|
+
async function runGscSyncSlice(opts) {
|
|
186
|
+
const rowLimit = opts.rowLimit ?? 500;
|
|
187
|
+
const cpuBudgetMs = opts.cpuBudgetMs ?? 2e4;
|
|
188
|
+
const maxPages = opts.maxPages ?? Infinity;
|
|
189
|
+
const searchType = opts.searchType ?? "web";
|
|
190
|
+
const dimensions = [...DIMENSIONS_BY_TABLE[opts.table]];
|
|
191
|
+
const dimensionFilterGroups = buildDomainFilterGroups(opts.domainFilter);
|
|
192
|
+
const loopStart = Date.now();
|
|
193
|
+
let startRow = opts.initialStartRow ?? 0;
|
|
194
|
+
let totalRows = 0;
|
|
195
|
+
let pageCount = 0;
|
|
196
|
+
while (true) {
|
|
197
|
+
if (pageCount >= maxPages) return {
|
|
198
|
+
totalRows,
|
|
199
|
+
hasMore: true,
|
|
200
|
+
nextStartRow: startRow
|
|
201
|
+
};
|
|
202
|
+
if (Date.now() - loopStart >= cpuBudgetMs) return {
|
|
203
|
+
totalRows,
|
|
204
|
+
hasMore: true,
|
|
205
|
+
nextStartRow: startRow
|
|
206
|
+
};
|
|
207
|
+
const query = {
|
|
208
|
+
startDate: opts.startDate,
|
|
209
|
+
endDate: opts.endDate,
|
|
210
|
+
dimensions,
|
|
211
|
+
rowLimit,
|
|
212
|
+
startRow,
|
|
213
|
+
dataState: "all",
|
|
214
|
+
searchType,
|
|
215
|
+
...dimensionFilterGroups ? { dimensionFilterGroups } : {}
|
|
216
|
+
};
|
|
217
|
+
const response = await opts.client._rawQuery(opts.siteUrl, query).catch((err) => {
|
|
218
|
+
if (isTimeoutLike(err)) return null;
|
|
219
|
+
throw err;
|
|
220
|
+
});
|
|
221
|
+
if (!response) return {
|
|
222
|
+
totalRows,
|
|
223
|
+
hasMore: true,
|
|
224
|
+
nextStartRow: startRow
|
|
225
|
+
};
|
|
226
|
+
const rows = response.rows ?? [];
|
|
227
|
+
totalRows += rows.length;
|
|
228
|
+
pageCount++;
|
|
229
|
+
opts.onPage?.({
|
|
230
|
+
searchType,
|
|
231
|
+
rowsThisPage: rows.length
|
|
232
|
+
});
|
|
233
|
+
if (rows.length > 0) await opts.onBatch(rows).catch((err) => {
|
|
234
|
+
if (isTimeoutLike(err)) return;
|
|
235
|
+
throw err;
|
|
236
|
+
});
|
|
237
|
+
if (rows.length < rowLimit) break;
|
|
238
|
+
startRow += rows.length;
|
|
239
|
+
}
|
|
240
|
+
return {
|
|
241
|
+
totalRows,
|
|
242
|
+
hasMore: false,
|
|
243
|
+
nextStartRow: startRow
|
|
244
|
+
};
|
|
245
|
+
}
|
|
246
|
+
export { canProxyToGsc, createGscApiQuerySource, createLiveGscSource, fetchGscDaily, fetchGscTopN, 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.17.
|
|
4
|
+
"version": "0.17.4",
|
|
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,8 +36,8 @@
|
|
|
36
36
|
"node": ">=18"
|
|
37
37
|
},
|
|
38
38
|
"dependencies": {
|
|
39
|
-
"@gscdump/engine": "0.17.
|
|
40
|
-
"gscdump": "0.17.
|
|
39
|
+
"@gscdump/engine": "0.17.4",
|
|
40
|
+
"gscdump": "0.17.4"
|
|
41
41
|
},
|
|
42
42
|
"devDependencies": {
|
|
43
43
|
"vitest": "^4.1.6"
|