gscdump 0.17.5 → 0.18.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.
@@ -16,7 +16,14 @@ interface TenantCtx {
16
16
  userId: string;
17
17
  siteId?: string;
18
18
  }
19
- type GscSearchAnalyticsDimension = 'page' | 'query' | 'country' | 'device' | 'date' | 'searchAppearance';
19
+ type GscSearchAnalyticsDimension = 'page' | 'query' | 'country' | 'device' | 'date' | 'hour' | 'searchAppearance';
20
+ type GscDataState = 'final' | 'all' | 'hourly_all';
21
+ interface GscSearchAnalyticsMetadata {
22
+ /** First date (YYYY-MM-DD, PT) still being collected. Populated when dataState=`all` and grouped by `date`. */
23
+ first_incomplete_date?: string;
24
+ /** First hour (YYYY-MM-DDThh:mm:ss±hh:mm, PT) still being collected. Populated when dataState=`hourly_all` and grouped by `hour`. */
25
+ first_incomplete_hour?: string;
26
+ }
20
27
  type GscSearchAnalyticsFilterOperator = 'equals' | 'notEquals' | 'contains' | 'notContains' | 'includingRegex' | 'excludingRegex';
21
28
  interface GscSearchAnalyticsFilter {
22
29
  dimension: GscSearchAnalyticsDimension;
@@ -28,6 +35,8 @@ interface GscSearchAnalyticsFilterGroup {
28
35
  filters: GscSearchAnalyticsFilter[];
29
36
  }
30
37
  type GscSearchType = 'web' | 'image' | 'video' | 'news' | 'discover' | 'googleNews';
38
+ type GscAggregationType = 'auto' | 'byPage' | 'byProperty' | 'byNewsShowcasePanel';
39
+ type GscResponseAggregationType = 'auto' | 'byPage' | 'byProperty' | 'byNewsShowcasePanel';
31
40
  interface GscSearchAnalyticsRequest {
32
41
  startDate: string;
33
42
  endDate: string;
@@ -35,7 +44,10 @@ interface GscSearchAnalyticsRequest {
35
44
  dimensionFilterGroups?: GscSearchAnalyticsFilterGroup[];
36
45
  rowLimit?: number;
37
46
  startRow?: number;
38
- searchType?: GscSearchType;
47
+ /** GSC search corpus. Maps to wire field `type` (the API still accepts the deprecated `searchType` alias). */
48
+ type?: GscSearchType;
49
+ dataState?: GscDataState;
50
+ aggregationType?: GscAggregationType;
39
51
  }
40
52
  interface GscSearchAnalyticsRow {
41
53
  keys: string[];
@@ -46,6 +58,7 @@ interface GscSearchAnalyticsRow {
46
58
  }
47
59
  interface GscSearchAnalyticsResponse {
48
60
  rows?: GscSearchAnalyticsRow[];
49
- responseAggregationType?: string;
61
+ responseAggregationType?: GscResponseAggregationType;
62
+ metadata?: GscSearchAnalyticsMetadata;
50
63
  }
51
- export { ColumnDef, ColumnType, GscSearchAnalyticsDimension, GscSearchAnalyticsFilter, GscSearchAnalyticsFilterGroup, GscSearchAnalyticsFilterOperator, GscSearchAnalyticsRequest, GscSearchAnalyticsResponse, GscSearchAnalyticsRow, GscSearchType, Row, TableName, TableSchema, TenantCtx };
64
+ export { ColumnDef, ColumnType, GscAggregationType, GscDataState, GscResponseAggregationType, GscSearchAnalyticsDimension, GscSearchAnalyticsFilter, GscSearchAnalyticsFilterGroup, GscSearchAnalyticsFilterOperator, GscSearchAnalyticsMetadata, GscSearchAnalyticsRequest, GscSearchAnalyticsResponse, GscSearchAnalyticsRow, GscSearchType, Row, TableName, TableSchema, TenantCtx };
package/dist/index.d.mts CHANGED
@@ -12,7 +12,14 @@ declare function runSequentialBatch<I, R>(items: I[], operation: (item: I, index
12
12
  concurrency?: number;
13
13
  onProgress?: (result: R, index: number, total: number) => void;
14
14
  }): Promise<R[]>;
15
- type GscSearchAnalyticsDimension = 'page' | 'query' | 'country' | 'device' | 'date' | 'searchAppearance';
15
+ type GscSearchAnalyticsDimension = 'page' | 'query' | 'country' | 'device' | 'date' | 'hour' | 'searchAppearance';
16
+ type GscDataState = 'final' | 'all' | 'hourly_all';
17
+ interface GscSearchAnalyticsMetadata {
18
+ /** First date (YYYY-MM-DD, PT) still being collected. Populated when dataState=`all` and grouped by `date`. */
19
+ first_incomplete_date?: string;
20
+ /** First hour (YYYY-MM-DDThh:mm:ss±hh:mm, PT) still being collected. Populated when dataState=`hourly_all` and grouped by `hour`. */
21
+ first_incomplete_hour?: string;
22
+ }
16
23
  type GscSearchAnalyticsFilterOperator = 'equals' | 'notEquals' | 'contains' | 'notContains' | 'includingRegex' | 'excludingRegex';
17
24
  interface GscSearchAnalyticsFilter {
18
25
  dimension: GscSearchAnalyticsDimension;
@@ -24,6 +31,8 @@ interface GscSearchAnalyticsFilterGroup {
24
31
  filters: GscSearchAnalyticsFilter[];
25
32
  }
26
33
  type GscSearchType = 'web' | 'image' | 'video' | 'news' | 'discover' | 'googleNews';
34
+ type GscAggregationType = 'auto' | 'byPage' | 'byProperty' | 'byNewsShowcasePanel';
35
+ type GscResponseAggregationType = 'auto' | 'byPage' | 'byProperty' | 'byNewsShowcasePanel';
27
36
  interface GscSearchAnalyticsRequest {
28
37
  startDate: string;
29
38
  endDate: string;
@@ -31,7 +40,10 @@ interface GscSearchAnalyticsRequest {
31
40
  dimensionFilterGroups?: GscSearchAnalyticsFilterGroup[];
32
41
  rowLimit?: number;
33
42
  startRow?: number;
34
- searchType?: GscSearchType;
43
+ /** GSC search corpus. Maps to wire field `type` (the API still accepts the deprecated `searchType` alias). */
44
+ type?: GscSearchType;
45
+ dataState?: GscDataState;
46
+ aggregationType?: GscAggregationType;
35
47
  }
36
48
  declare const _default: {
37
49
  name: string;
@@ -64,6 +76,8 @@ interface DimensionValueMap {
64
76
  device: Device;
65
77
  searchAppearance: string;
66
78
  date: string;
79
+ /** Hour bucket — ISO-8601 with PT offset, e.g. `2025-07-14T13:00:00-07:00`. Use with `dataState: 'hourly_all'`. */
80
+ hour: string;
67
81
  }
68
82
  type Dimension = keyof DimensionValueMap;
69
83
  interface QueryParamValueMap {
@@ -113,6 +127,12 @@ interface BuilderState {
113
127
  };
114
128
  rowLimit?: number;
115
129
  startRow?: number;
130
+ /** GSC `dataState`. `'hourly_all'` is required when grouping by `hour`. */
131
+ dataState?: GscDataState;
132
+ /** GSC `aggregationType`. `'byNewsShowcasePanel'` requires `type=discover|googleNews` with NEWS_SHOWCASE searchAppearance. */
133
+ aggregationType?: GscAggregationType;
134
+ /** GSC search corpus. Wins over any `searchType` filter when both are set. */
135
+ searchType?: SearchType;
116
136
  }
117
137
  type SelectableColumn = Column<Dimension> | MetricColumn<Metric>;
118
138
  type OrderableColumn = MetricColumn<Metric> | Column<'date'>;
@@ -127,6 +147,10 @@ interface GSCQueryBuilder<D extends Dimension[] = [], C = object> {
127
147
  orderBy: (col: OrderableColumn, dir: 'asc' | 'desc') => GSCQueryBuilder<D, C>;
128
148
  limit: (n: number) => GSCQueryBuilder<D, C>;
129
149
  offset: (n: number) => GSCQueryBuilder<D, C>;
150
+ dataState: (state: GscDataState) => GSCQueryBuilder<D, C>;
151
+ aggregationType: (type: GscAggregationType) => GSCQueryBuilder<D, C>;
152
+ /** GSC search corpus (`type` on the wire). Equivalent to filtering by `searchType`. */
153
+ type: (t: GscSearchType) => GSCQueryBuilder<D, C>;
130
154
  toBody: () => GscSearchAnalyticsRequest;
131
155
  getState: () => BuilderState;
132
156
  }
@@ -239,16 +263,23 @@ declare function createFetch(auth: Auth, options?: FetchOptions): $Fetch;
239
263
  interface CallOptions {
240
264
  signal?: AbortSignal;
241
265
  }
266
+ /** Generator return value for `client.query` — exposes API response metadata plus the resolved aggregation type Google actually used (may differ from the requested `aggregationType: 'auto'`). */
267
+ interface QueryReturn {
268
+ metadata?: GscSearchAnalyticsMetadata;
269
+ /** Aggregation type Google actually used. Useful when requesting `auto` to know if rows were aggregated `byPage` vs `byProperty`. */
270
+ responseAggregationType?: GscResponseAggregationType;
271
+ }
242
272
  interface GoogleSearchConsoleClient {
243
273
  /** Query search analytics with builder, returns async generator yielding typed row batches */
244
- query: <D extends Dimension[], C>(siteUrl: string, builder: GSCQueryBuilder<D, C>, opts?: CallOptions) => AsyncGenerator<GSCRow<D, C>[]>;
274
+ query: <D extends Dimension[], C>(siteUrl: string, builder: GSCQueryBuilder<D, C>, opts?: CallOptions) => AsyncGenerator<GSCRow<D, C>[], QueryReturn>;
245
275
  /**
246
276
  * List all sites. Also exposes write ops as `client.sites.add(siteUrl)` and
247
277
  * `client.sites.delete(siteUrl)`. Calling `client.sites()` is equivalent to
248
278
  * `client.sites.list()`.
249
279
  */
250
280
  sites: ((opts?: CallOptions) => Promise<ApiSite[]>) & {
251
- list: (opts?: CallOptions) => Promise<ApiSite[]>; /** Add a property in unverified state. Caller must verify ownership separately. */
281
+ list: (opts?: CallOptions) => Promise<ApiSite[]>; /** Retrieve a single property (with permission level). 404 if not in the user's account. */
282
+ get: (siteUrl: string, opts?: CallOptions) => Promise<ApiSite>; /** Add a property in unverified state. Caller must verify ownership separately. */
252
283
  add: (siteUrl: string, opts?: CallOptions) => Promise<void>; /** Remove a property from the user's account. */
253
284
  delete: (siteUrl: string, opts?: CallOptions) => Promise<void>;
254
285
  };
@@ -266,11 +297,15 @@ interface GoogleSearchConsoleClient {
266
297
  get: (id: string, opts?: CallOptions) => Promise<VerificationWebResource>;
267
298
  delete: (id: string, opts?: CallOptions) => Promise<void>;
268
299
  };
269
- /** Inspect a URL */
270
- inspect: (siteUrl: string, url: string, opts?: CallOptions) => Promise<InspectUrlIndexResponse>;
300
+ /** Inspect a URL. `languageCode` is a BCP-47 tag for translating result strings; omit to let Google pick. */
301
+ inspect: (siteUrl: string, url: string, opts?: CallOptions & {
302
+ languageCode?: string;
303
+ }) => Promise<InspectUrlIndexResponse>;
271
304
  /** Sitemap operations */
272
305
  sitemaps: {
273
- list: (siteUrl: string, opts?: CallOptions) => Promise<ApiSitemap[]>;
306
+ /** List sitemaps. Pass `sitemapIndex` to list children of a sitemap-index file. */list: (siteUrl: string, opts?: CallOptions & {
307
+ sitemapIndex?: string;
308
+ }) => Promise<ApiSitemap[]>;
274
309
  get: (siteUrl: string, feedpath: string, opts?: CallOptions) => Promise<ApiSitemap>;
275
310
  submit: (siteUrl: string, feedpath: string, opts?: CallOptions) => Promise<void>;
276
311
  delete: (siteUrl: string, feedpath: string, opts?: CallOptions) => Promise<void>;
@@ -363,6 +398,14 @@ interface ParsedIndexingResult {
363
398
  mobileIssues: string | null;
364
399
  richResultsVerdict: string | null;
365
400
  richResultsItems: string | null;
401
+ ampVerdict: string | null;
402
+ ampUrl: string | null;
403
+ ampIndexingState: string | null;
404
+ ampIndexStatusVerdict: string | null;
405
+ ampRobotsTxtState: string | null;
406
+ ampPageFetchState: string | null;
407
+ ampLastCrawlTime: string | null;
408
+ ampIssues: string | null;
366
409
  inspectionResultLink: string | null;
367
410
  }
368
411
  declare function inspectUrlFlat(client: GoogleSearchConsoleClient, siteUrl: string, inspectionUrl: string): Promise<ParsedIndexingResult>;
@@ -406,7 +449,7 @@ declare function fetchSites(client: GoogleSearchConsoleClient): Promise<ApiSite[
406
449
  * Fetches all verified sites with their sitemaps from Google Search Console.
407
450
  */
408
451
  declare function fetchSitesWithSitemaps(client: GoogleSearchConsoleClient): Promise<(Site & {
409
- sitemaps: RequiredNonNullable<ApiSitemap>[];
452
+ sitemaps: ApiSitemap[];
410
453
  })[]>;
411
454
  /**
412
455
  * Fetches all sitemaps for a site.
@@ -690,8 +733,8 @@ declare function findBestGscProperty<T extends GscPropertyCandidate>(targetDomai
690
733
  };
691
734
  declare function findExactGscProperty<T extends GscPropertyCandidate>(propertyUrl: string, properties: readonly T[]): T | null;
692
735
  declare function formatGscPropertyCandidates(candidates: ReadonlyArray<GscPropertyCandidate | null | undefined>): string;
693
- declare const INDEXING_DAILY_LIMIT = 2000;
694
- declare const INDEXING_EFFECTIVE_LIMIT = 1800;
736
+ declare const URL_INSPECTION_DAILY_LIMIT = 2000;
737
+ declare const URL_INSPECTION_EFFECTIVE_LIMIT = 1800;
695
738
  declare function hasGscReadScope(scopes: string | null | undefined): boolean;
696
739
  declare function hasGscWriteScope(scopes: string | null | undefined): boolean;
697
740
  declare function hasIndexingScope(scopes: string | null | undefined): boolean;
@@ -850,4 +893,4 @@ interface FetchSitemapUrlsOptions extends DiscoverSitemapOptions {
850
893
  * `<loc>https://...</loc>` shape but doesn't validate the schema.
851
894
  */
852
895
  declare function fetchSitemapUrls(sitemapUrl: string, options?: FetchSitemapUrlsOptions): Promise<string[]>;
853
- export { AccountNextAction, AccountStatus, AnalyticsNextAction, AnalyticsStatus, ApiSite, ApiSitemap, ApiSitemapContent, Auth, AuthClient, AuthOptions, BackfillProgress, CallOptions, DAYS_PER_RANGE, DataRow, DimensionFilter, DimensionFilterGroup, DiscoverSitemapOptions, FetchSitemapUrlsOptions, GSCDUMP_ONBOARDING_CONTRACT_VERSION, GSCDUMP_OPTIONAL_INDEXING_SCOPE, GSCDUMP_REQUIRED_ANALYTICS_SCOPE, GSC_FINALIZED_LAG_DAYS, GSC_FRESHEST_LAG_DAYS, GSC_QUOTAS, GSC_RETENTION_MONTHS, GoogleSearchConsoleClient, GoogleSearchConsoleClientOptions, GscApiError, GscApiErrorInfo, GscError, GscErrorKind, GscPropertyCandidate, GscdumpApiOptions, INDEXING_DAILY_LIMIT, INDEXING_EFFECTIVE_LIMIT, INDEXING_ISSUE_FILTERS, INDEXING_ISSUE_LABELS, INDEXING_ISSUE_SEVERITY, IndexStatusResult, IndexingEligibility, IndexingIneligibleReason, IndexingIssueType, IndexingMetadata, IndexingNextAction, IndexingNotificationType, IndexingResult, IndexingStatus, InspectUrlIndexResponse, InspectUrlResult, InspectionPriority, LifecycleError, LifecycleErrorCode, LifecycleProgress, LifecycleWebhookEnvelope, LifecycleWebhookEvent, MS_PER_DAY, MobileUsabilityResult, OAuthTokens, ParsedGscSiteUrl, ParsedIndexingResult, PartnerLifecycleAccount, PartnerLifecycleResponse, PartnerLifecycleSite, Period, PropertyNextAction, PropertyStatus, PublishUrlNotificationResponse, QuerySourceMode, RequiredNonNullable, ResolvedAnalyticsRange, RichResultsResult, SearchAnalyticsQuery, SearchAnalyticsResponse, Site, SiteAnalytics, SitemapNextAction, SitemapStatus, UrlInspectionResult, UrlNotificationMetadata, VerificationMethod, VerificationSite, VerificationSiteType, VerificationToken, VerificationWebResource, accountNextActions, accountStatuses, addDays, addSite, analyticsNextActions, analyticsStatuses, batchInspectUrls, batchRequestIndexing, canUseUrlInspection, classifyError, countDays, createAuth, createFetch, daysAgo, deleteSite, deleteSitemap, discoverSitemap, exchangeAuthCode, fetchSitemap, fetchSitemapUrls, fetchSitemaps, fetchSites, fetchSitesWithSitemaps, findBestGscProperty, findExactGscProperty, formatErrorForCli, formatGscPropertyCandidates, generateGscDateRange, getBackfillProgress, getDateRange, getFreshestGscDate, getIndexingEligibility, getIndexingMetadata, getLatestGscDate, getNextCheckAfter, getNextCheckPriority, getNextDate, getOldestGscDate, getPendingDates, getPreviousDate, getPstDate, getVerificationToken, getVerifiedSite, googleSearchConsole, grantedScopeList, groupIntoRanges, gscPropertyMatchesTarget, gscdumpApi, hasGscReadScope, hasGscWriteScope, hasIndexingScope, hasOptionalIndexingScope, hasRequiredAnalyticsScope, indexingNextActions, indexingStatuses, inspectUrl, inspectUrlFlat, isPermissionDeniedError, isValidGscDate, isVerifiedGscPermission, isVerifiedGscProperty, lifecycleErrorCodes, lifecycleWebhookEvents, listVerifiedSites, matchGscSite, normalizeGscSiteUrl, normalizeRegistrationTarget, parseGoogleError, parseGrantedScopes, parseGscSiteUrl, pickBestGscProperty, progressBar, propertyNextActions, propertyStatuses, querySourceModes, refreshAccessToken, requestIndexing, rethrowAsGscApiError, rowWithMetricDefaults, runSequentialBatch, siteUrlToVerificationSite, sitemapNextActions, sitemapStatuses, storageError, submitSitemap, toIsoDate, unverifySite, verificationMethodsFor, verifySite };
896
+ export { AccountNextAction, AccountStatus, AnalyticsNextAction, AnalyticsStatus, ApiSite, ApiSitemap, ApiSitemapContent, Auth, AuthClient, AuthOptions, BackfillProgress, CallOptions, DAYS_PER_RANGE, DataRow, DimensionFilter, DimensionFilterGroup, DiscoverSitemapOptions, FetchSitemapUrlsOptions, GSCDUMP_ONBOARDING_CONTRACT_VERSION, GSCDUMP_OPTIONAL_INDEXING_SCOPE, GSCDUMP_REQUIRED_ANALYTICS_SCOPE, GSC_FINALIZED_LAG_DAYS, GSC_FRESHEST_LAG_DAYS, GSC_QUOTAS, GSC_RETENTION_MONTHS, GoogleSearchConsoleClient, GoogleSearchConsoleClientOptions, GscApiError, GscApiErrorInfo, GscError, GscErrorKind, GscPropertyCandidate, GscdumpApiOptions, INDEXING_ISSUE_FILTERS, INDEXING_ISSUE_LABELS, INDEXING_ISSUE_SEVERITY, IndexStatusResult, IndexingEligibility, IndexingIneligibleReason, IndexingIssueType, IndexingMetadata, IndexingNextAction, IndexingNotificationType, IndexingResult, IndexingStatus, InspectUrlIndexResponse, InspectUrlResult, InspectionPriority, LifecycleError, LifecycleErrorCode, LifecycleProgress, LifecycleWebhookEnvelope, LifecycleWebhookEvent, MS_PER_DAY, MobileUsabilityResult, OAuthTokens, ParsedGscSiteUrl, ParsedIndexingResult, PartnerLifecycleAccount, PartnerLifecycleResponse, PartnerLifecycleSite, Period, PropertyNextAction, PropertyStatus, PublishUrlNotificationResponse, QueryReturn, QuerySourceMode, RequiredNonNullable, ResolvedAnalyticsRange, RichResultsResult, SearchAnalyticsQuery, SearchAnalyticsResponse, Site, SiteAnalytics, SitemapNextAction, SitemapStatus, URL_INSPECTION_DAILY_LIMIT, URL_INSPECTION_EFFECTIVE_LIMIT, UrlInspectionResult, UrlNotificationMetadata, VerificationMethod, VerificationSite, VerificationSiteType, VerificationToken, VerificationWebResource, accountNextActions, accountStatuses, addDays, addSite, analyticsNextActions, analyticsStatuses, batchInspectUrls, batchRequestIndexing, canUseUrlInspection, classifyError, countDays, createAuth, createFetch, daysAgo, deleteSite, deleteSitemap, discoverSitemap, exchangeAuthCode, fetchSitemap, fetchSitemapUrls, fetchSitemaps, fetchSites, fetchSitesWithSitemaps, findBestGscProperty, findExactGscProperty, formatErrorForCli, formatGscPropertyCandidates, generateGscDateRange, getBackfillProgress, getDateRange, getFreshestGscDate, getIndexingEligibility, getIndexingMetadata, getLatestGscDate, getNextCheckAfter, getNextCheckPriority, getNextDate, getOldestGscDate, getPendingDates, getPreviousDate, getPstDate, getVerificationToken, getVerifiedSite, googleSearchConsole, grantedScopeList, groupIntoRanges, gscPropertyMatchesTarget, gscdumpApi, hasGscReadScope, hasGscWriteScope, hasIndexingScope, hasOptionalIndexingScope, hasRequiredAnalyticsScope, indexingNextActions, indexingStatuses, inspectUrl, inspectUrlFlat, isPermissionDeniedError, isValidGscDate, isVerifiedGscPermission, isVerifiedGscProperty, lifecycleErrorCodes, lifecycleWebhookEvents, listVerifiedSites, matchGscSite, normalizeGscSiteUrl, normalizeRegistrationTarget, parseGoogleError, parseGrantedScopes, parseGscSiteUrl, pickBestGscProperty, progressBar, propertyNextActions, propertyStatuses, querySourceModes, refreshAccessToken, requestIndexing, rethrowAsGscApiError, rowWithMetricDefaults, runSequentialBatch, siteUrlToVerificationSite, sitemapNextActions, sitemapStatuses, storageError, submitSitemap, toIsoDate, unverifySite, verificationMethodsFor, verifySite };
package/dist/index.mjs CHANGED
@@ -50,17 +50,26 @@ async function batchRequestIndexing(client, urls, options = {}) {
50
50
  onProgress
51
51
  });
52
52
  }
53
+ const WEBMASTERS_READ = "https://www.googleapis.com/auth/webmasters.readonly";
54
+ const WEBMASTERS_WRITE = "https://www.googleapis.com/auth/webmasters";
55
+ const INDEXING = "https://www.googleapis.com/auth/indexing";
56
+ function tokenize(scopes) {
57
+ if (!scopes) return [];
58
+ return scopes.split(/\s+/).filter(Boolean);
59
+ }
60
+ function hasScope(scopes, scope) {
61
+ const tokens = tokenize(scopes);
62
+ const suffix = scope.replace("https://www.googleapis.com/auth/", "");
63
+ return tokens.includes(scope) || tokens.includes(suffix);
64
+ }
53
65
  function hasGscReadScope(scopes) {
54
- if (!scopes) return false;
55
- return scopes.includes("webmasters.readonly") || scopes.includes("webmasters");
66
+ return hasScope(scopes, WEBMASTERS_READ) || hasScope(scopes, WEBMASTERS_WRITE);
56
67
  }
57
68
  function hasGscWriteScope(scopes) {
58
- if (!scopes) return false;
59
- return scopes.includes("webmasters") && !scopes.includes("webmasters.readonly");
69
+ return hasScope(scopes, WEBMASTERS_WRITE);
60
70
  }
61
71
  function hasIndexingScope(scopes) {
62
- if (!scopes) return false;
63
- return scopes.includes("googleapis.com/auth/indexing");
72
+ return hasScope(scopes, INDEXING);
64
73
  }
65
74
  async function inspectUrl(client, siteUrl, inspectionUrl) {
66
75
  const inspection = (await client.inspect(siteUrl, inspectionUrl)).inspectionResult;
@@ -89,6 +98,7 @@ async function inspectUrlFlat(client, siteUrl, inspectionUrl) {
89
98
  const index = inspection?.indexStatusResult;
90
99
  const mobile = inspection?.mobileUsabilityResult;
91
100
  const rich = inspection?.richResultsResult;
101
+ const amp = inspection?.ampResult;
92
102
  return {
93
103
  url: inspectionUrl,
94
104
  verdict: index?.verdict ?? null,
@@ -106,6 +116,14 @@ async function inspectUrlFlat(client, siteUrl, inspectionUrl) {
106
116
  mobileIssues: mobile?.issues?.length ? JSON.stringify(mobile.issues) : null,
107
117
  richResultsVerdict: rich?.verdict ?? null,
108
118
  richResultsItems: rich?.detectedItems?.length ? JSON.stringify(rich.detectedItems) : null,
119
+ ampVerdict: amp?.verdict ?? null,
120
+ ampUrl: amp?.ampUrl ?? null,
121
+ ampIndexingState: amp?.indexingState ?? null,
122
+ ampIndexStatusVerdict: amp?.ampIndexStatusVerdict ?? null,
123
+ ampRobotsTxtState: amp?.robotsTxtState ?? null,
124
+ ampPageFetchState: amp?.pageFetchState ?? null,
125
+ ampLastCrawlTime: amp?.lastCrawlTime ?? null,
126
+ ampIssues: amp?.issues?.length ? JSON.stringify(amp.issues) : null,
109
127
  inspectionResultLink: inspection?.inspectionResultLink ?? null
110
128
  };
111
129
  }
@@ -123,7 +141,11 @@ function getNextCheckAfter(priority) {
123
141
  case "low": return now + 30 * 86400;
124
142
  }
125
143
  }
126
- const INSPECTION_ALLOWED_PERMISSIONS = new Set(["siteOwner", "siteFullUser"]);
144
+ const INSPECTION_ALLOWED_PERMISSIONS = new Set([
145
+ "siteOwner",
146
+ "siteFullUser",
147
+ "siteRestrictedUser"
148
+ ]);
127
149
  function canUseUrlInspection(permissionLevel) {
128
150
  return !!permissionLevel && INSPECTION_ALLOWED_PERMISSIONS.has(permissionLevel);
129
151
  }
@@ -216,6 +238,44 @@ function extractRetryAfter(error) {
216
238
  }
217
239
  }
218
240
  const QUOTA_MESSAGE_RE = /quota|rate\s*limit/i;
241
+ const QUOTA_REASONS = new Set([
242
+ "dailyLimitExceeded",
243
+ "dailyLimitExceededUnreg",
244
+ "rateLimitExceeded",
245
+ "rateLimitExceededUnreg",
246
+ "userRateLimitExceeded",
247
+ "userRateLimitExceededUnreg",
248
+ "quotaExceeded",
249
+ "concurrentLimitExceeded",
250
+ "variableTermLimitExceeded",
251
+ "variableTermExpiredDailyExceeded",
252
+ "servingLimitExceeded",
253
+ "responseTooLarge",
254
+ "limitExceeded",
255
+ "batchSizeTooLarge",
256
+ "RATE_LIMIT_EXCEEDED",
257
+ "RESOURCE_EXHAUSTED"
258
+ ]);
259
+ function extractReason(cause) {
260
+ const data = pickField(cause, [["data"]], (_v) => true);
261
+ if (data) {
262
+ const errorInfo = pickField(data, [["error", "details"]], (v) => Array.isArray(v));
263
+ if (errorInfo) {
264
+ const reason = errorInfo.find((d) => typeof d["@type"] === "string" && d["@type"].includes("ErrorInfo"))?.reason;
265
+ if (typeof reason === "string") return reason;
266
+ }
267
+ const directErrors = pickField(data, [["error", "errors"]], (v) => Array.isArray(v));
268
+ if (directErrors) {
269
+ const reason = directErrors[0]?.reason;
270
+ if (typeof reason === "string") return reason;
271
+ }
272
+ }
273
+ }
274
+ function isQuotaCondition(cause, message) {
275
+ const reason = extractReason(cause);
276
+ if (reason && QUOTA_REASONS.has(reason)) return true;
277
+ return QUOTA_MESSAGE_RE.test(message);
278
+ }
219
279
  function classifyError(cause) {
220
280
  const status = extractStatus(cause);
221
281
  const message = extractMessage(cause);
@@ -231,7 +291,7 @@ function classifyError(cause) {
231
291
  cause
232
292
  };
233
293
  if (status === 403) {
234
- if (QUOTA_MESSAGE_RE.test(message)) return {
294
+ if (isQuotaCondition(cause, message)) return {
235
295
  kind: "rate-limited",
236
296
  message,
237
297
  retryAfter: extractRetryAfter(cause),
@@ -243,12 +303,12 @@ function classifyError(cause) {
243
303
  cause
244
304
  };
245
305
  }
246
- if (status === 404) return {
306
+ if (status === 404 || status === 410) return {
247
307
  kind: "not-found",
248
308
  message,
249
309
  cause
250
310
  };
251
- if (status === 400 || status === 422) return {
311
+ if (status === 400 || status === 402 || status === 409 || status === 413 || status === 422) return {
252
312
  kind: "validation",
253
313
  message,
254
314
  cause
@@ -412,7 +472,7 @@ async function fetchSites(client) {
412
472
  async function fetchSitesWithSitemaps(client) {
413
473
  const sites = (await client.sites()).filter((s) => !!s.siteUrl && s.permissionLevel !== "siteUnverifiedUser");
414
474
  return Promise.all(sites.map(async (site) => {
415
- const sitemaps = site.permissionLevel === "siteOwner" ? await client.sitemaps.list(site.siteUrl) : [];
475
+ const sitemaps = await client.sitemaps.list(site.siteUrl).catch(() => []);
416
476
  return {
417
477
  ...site,
418
478
  sitemaps
@@ -2152,9 +2212,9 @@ function extractSpecialFilters(filter) {
2152
2212
  else otherFilters.push(f);
2153
2213
  if (filter._nestedGroups) for (const nested of filter._nestedGroups) {
2154
2214
  const extracted = extractSpecialFilters(nested);
2155
- if (extracted.startDate) startDate = extracted.startDate;
2156
- if (extracted.endDate) endDate = extracted.endDate;
2157
- if (extracted.searchType) searchType = extracted.searchType;
2215
+ if (!startDate && extracted.startDate) startDate = extracted.startDate;
2216
+ if (!endDate && extracted.endDate) endDate = extracted.endDate;
2217
+ if (!searchType && extracted.searchType) searchType = extracted.searchType;
2158
2218
  if (extracted.dimensionFilter) cleanedNestedGroups.push(extracted.dimensionFilter);
2159
2219
  }
2160
2220
  const dimensionFilter = otherFilters.length > 0 || cleanedNestedGroups.length > 0 ? {
@@ -2177,11 +2237,40 @@ function resolveToBody(state) {
2177
2237
  startDate,
2178
2238
  endDate
2179
2239
  };
2180
- if (searchType) body.searchType = searchType;
2181
- if (state.rowLimit) body.rowLimit = state.rowLimit;
2182
- if (state.startRow) body.startRow = state.startRow;
2240
+ const resolvedType = state.searchType ?? searchType;
2241
+ if (resolvedType) body.type = resolvedType;
2242
+ if (state.rowLimit !== void 0) {
2243
+ if (!Number.isInteger(state.rowLimit) || state.rowLimit < 1) throw new Error(`rowLimit must be a positive integer, got ${state.rowLimit}`);
2244
+ body.rowLimit = state.rowLimit;
2245
+ }
2246
+ if (state.startRow !== void 0) {
2247
+ if (!Number.isInteger(state.startRow) || state.startRow < 0) throw new Error(`startRow must be a non-negative integer, got ${state.startRow}`);
2248
+ if (state.startRow > 0) body.startRow = state.startRow;
2249
+ }
2250
+ const hasHour = state.dimensions?.includes("hour");
2251
+ if (hasHour && state.dataState !== "hourly_all") throw new Error("hour dimension requires dataState: \"hourly_all\"");
2252
+ if (state.dataState === "hourly_all" && !hasHour) throw new Error("dataState: \"hourly_all\" requires grouping by hour dimension");
2253
+ if (state.dataState) body.dataState = state.dataState;
2183
2254
  const filterGroups = resolveFilter(dimensionFilter);
2184
2255
  if (filterGroups.length > 0) body.dimensionFilterGroups = filterGroups;
2256
+ if (state.aggregationType) {
2257
+ const groupsByPage = (body.dimensions ?? []).includes("page");
2258
+ const apiLeafFilters = filterGroups.flatMap((g) => g.filters ?? []);
2259
+ const filtersByPage = apiLeafFilters.some((f) => f.dimension === "page");
2260
+ if (state.aggregationType === "byProperty") {
2261
+ if (body.type === "discover" || body.type === "googleNews") throw new Error("aggregationType: \"byProperty\" is not supported for type \"discover\" or \"googleNews\"");
2262
+ if (groupsByPage || filtersByPage) throw new Error("aggregationType: \"byProperty\" is not allowed when grouping or filtering by page");
2263
+ }
2264
+ if (state.aggregationType === "byNewsShowcasePanel") {
2265
+ if (body.type !== "discover" && body.type !== "googleNews") throw new Error("aggregationType: \"byNewsShowcasePanel\" requires type \"discover\" or \"googleNews\"");
2266
+ if (groupsByPage || filtersByPage) throw new Error("aggregationType: \"byNewsShowcasePanel\" is not allowed when grouping or filtering by page");
2267
+ const saFilters = apiLeafFilters.filter((f) => f.dimension === "searchAppearance");
2268
+ const hasNewsShowcase = saFilters.some((f) => f.operator === "equals" && f.expression === "NEWS_SHOWCASE");
2269
+ const hasOther = saFilters.some((f) => !(f.operator === "equals" && f.expression === "NEWS_SHOWCASE"));
2270
+ if (!hasNewsShowcase || hasOther) throw new Error("aggregationType: \"byNewsShowcasePanel\" requires a searchAppearance equals \"NEWS_SHOWCASE\" filter and no other searchAppearance filter");
2271
+ }
2272
+ body.aggregationType = state.aggregationType;
2273
+ }
2185
2274
  return body;
2186
2275
  }
2187
2276
  function isApiFilter(f) {
@@ -2232,6 +2321,8 @@ function gscdumpApi(options) {
2232
2321
  retryDelay: 1e3,
2233
2322
  retryStatusCodes: [
2234
2323
  408,
2324
+ 409,
2325
+ 425,
2235
2326
  429,
2236
2327
  500,
2237
2328
  502,
@@ -2263,10 +2354,16 @@ function gscdumpApi(options) {
2263
2354
  async *query(siteId, builder, opts) {
2264
2355
  const state = builder.getState();
2265
2356
  const body = resolveToBody(state);
2266
- const rowLimit = body.rowLimit || 25e3;
2357
+ const totalCap = body.rowLimit;
2358
+ const pageSize = Math.min(totalCap ?? 25e3, 25e3);
2267
2359
  let startRow = body.startRow || 0;
2360
+ let yielded = 0;
2361
+ let metadata;
2268
2362
  while (true) {
2269
2363
  opts?.signal?.throwIfAborted();
2364
+ const remaining = totalCap ? totalCap - yielded : pageSize;
2365
+ if (remaining <= 0) break;
2366
+ const rowLimit = Math.min(pageSize, remaining);
2270
2367
  const response = await fetch(`/api/sites/${encodeURIComponent(siteId)}/query`, {
2271
2368
  method: "POST",
2272
2369
  body: {
@@ -2276,6 +2373,7 @@ function gscdumpApi(options) {
2276
2373
  },
2277
2374
  signal: opts?.signal
2278
2375
  });
2376
+ if (response.metadata) metadata = response.metadata;
2279
2377
  const rows = response.rows.map((row) => {
2280
2378
  const result = rowWithMetricDefaults(row);
2281
2379
  state.dimensions.forEach((dim) => {
@@ -2284,9 +2382,11 @@ function gscdumpApi(options) {
2284
2382
  return result;
2285
2383
  });
2286
2384
  yield rows;
2385
+ yielded += rows.length;
2287
2386
  if (!response.meta.hasMore || rows.length < rowLimit) break;
2288
2387
  startRow += rows.length;
2289
2388
  }
2389
+ return { metadata };
2290
2390
  },
2291
2391
  sites: (() => {
2292
2392
  const list = async (opts) => {
@@ -2300,6 +2400,7 @@ function gscdumpApi(options) {
2300
2400
  };
2301
2401
  return Object.assign(list, {
2302
2402
  list,
2403
+ get: unsupported("get"),
2303
2404
  add: unsupported("add"),
2304
2405
  delete: unsupported("delete")
2305
2406
  });
@@ -2352,6 +2453,15 @@ function gscdumpApi(options) {
2352
2453
  const GSC_API = "https://searchconsole.googleapis.com";
2353
2454
  const INDEXING_API = "https://indexing.googleapis.com";
2354
2455
  const SITE_VERIFICATION_API = "https://www.googleapis.com/siteVerification/v1";
2456
+ function encodeSiteUrl(siteUrl) {
2457
+ if (siteUrl.startsWith("sc-domain:")) return `sc-domain:${encodeURIComponent(siteUrl.slice(10))}`;
2458
+ return encodeURIComponent(siteUrl);
2459
+ }
2460
+ function assertValidSiteUrl(siteUrl) {
2461
+ if (siteUrl.startsWith("sc-domain:") && siteUrl.length > 10) return;
2462
+ if (/^https?:\/\/.+/.test(siteUrl)) return;
2463
+ throw new Error(`Invalid siteUrl: expected "https?://…" or "sc-domain:…", got "${siteUrl}"`);
2464
+ }
2355
2465
  function createAuth(options) {
2356
2466
  let credentials = { refresh_token: options.refreshToken };
2357
2467
  return {
@@ -2393,10 +2503,21 @@ function createFetch(auth, options) {
2393
2503
  return ofetch.create({
2394
2504
  ...options,
2395
2505
  retry: 3,
2396
- retryDelay: 1e3,
2506
+ retryDelay: (ctx) => {
2507
+ const status = ctx.response?.status;
2508
+ if (status === 429 || status === 503) {
2509
+ const header = ctx.response?.headers.get("retry-after");
2510
+ if (header) {
2511
+ const secs = Number.parseInt(header, 10);
2512
+ if (Number.isFinite(secs)) return secs * 1e3;
2513
+ const when = Date.parse(header);
2514
+ if (Number.isFinite(when)) return Math.max(0, when - Date.now());
2515
+ }
2516
+ }
2517
+ return 1e3;
2518
+ },
2397
2519
  retryStatusCodes: [
2398
2520
  408,
2399
- 409,
2400
2521
  425,
2401
2522
  429,
2402
2523
  500,
@@ -2439,7 +2560,7 @@ function googleSearchConsole(auth, options = {}) {
2439
2560
  }
2440
2561
  fetch = createFetch(authState, fetchOptions);
2441
2562
  }
2442
- const rawQuery = (siteUrl, body, opts) => fetch(`${GSC_API}/webmasters/v3/sites/${encodeURIComponent(siteUrl)}/searchAnalytics/query`, {
2563
+ const rawQuery = (siteUrl, body, opts) => fetch(`${GSC_API}/webmasters/v3/sites/${encodeSiteUrl(siteUrl)}/searchAnalytics/query`, {
2443
2564
  method: "POST",
2444
2565
  body,
2445
2566
  signal: opts?.signal
@@ -2448,25 +2569,40 @@ function googleSearchConsole(auth, options = {}) {
2448
2569
  async *query(siteUrl, builder, opts) {
2449
2570
  const state = builder.getState();
2450
2571
  const body = resolveToBody(state);
2451
- const rowLimit = body.rowLimit || 25e3;
2572
+ const totalCap = body.rowLimit;
2573
+ const pageSize = Math.min(totalCap ?? 25e3, 25e3);
2452
2574
  let startRow = body.startRow || 0;
2575
+ let yielded = 0;
2576
+ let metadata;
2577
+ let responseAggregationType;
2453
2578
  while (true) {
2454
2579
  opts?.signal?.throwIfAborted();
2455
- const rows = ((await rawQuery(siteUrl, {
2580
+ const remaining = totalCap ? totalCap - yielded : pageSize;
2581
+ if (remaining <= 0) break;
2582
+ const rowLimit = Math.min(pageSize, remaining);
2583
+ const response = await rawQuery(siteUrl, {
2456
2584
  ...body,
2457
2585
  startRow,
2458
2586
  rowLimit
2459
- }, opts)).rows || []).map((row) => {
2587
+ }, opts);
2588
+ if (response.metadata) metadata = response.metadata;
2589
+ if (response.responseAggregationType) responseAggregationType = response.responseAggregationType;
2590
+ const rows = (response.rows || []).map((row) => {
2460
2591
  const result = rowWithMetricDefaults(row);
2461
2592
  state.dimensions.forEach((dim, i) => {
2462
2593
  result[dim] = row.keys?.[i];
2463
2594
  });
2464
2595
  return result;
2465
2596
  });
2597
+ if (rows.length === 0) break;
2466
2598
  yield rows;
2467
- if (rows.length < rowLimit) break;
2599
+ yielded += rows.length;
2468
2600
  startRow += rows.length;
2469
2601
  }
2602
+ return {
2603
+ metadata,
2604
+ responseAggregationType
2605
+ };
2470
2606
  },
2471
2607
  sites: (() => {
2472
2608
  const list = async (opts) => {
@@ -2474,11 +2610,15 @@ function googleSearchConsole(auth, options = {}) {
2474
2610
  };
2475
2611
  return Object.assign(list, {
2476
2612
  list,
2477
- add: (siteUrl, opts) => fetch(`${GSC_API}/webmasters/v3/sites/${encodeURIComponent(siteUrl)}`, {
2478
- method: "PUT",
2479
- signal: opts?.signal
2480
- }),
2481
- delete: (siteUrl, opts) => fetch(`${GSC_API}/webmasters/v3/sites/${encodeURIComponent(siteUrl)}`, {
2613
+ get: (siteUrl, opts) => fetch(`${GSC_API}/webmasters/v3/sites/${encodeSiteUrl(siteUrl)}`, { signal: opts?.signal }),
2614
+ add: (siteUrl, opts) => {
2615
+ assertValidSiteUrl(siteUrl);
2616
+ return fetch(`${GSC_API}/webmasters/v3/sites/${encodeSiteUrl(siteUrl)}`, {
2617
+ method: "PUT",
2618
+ signal: opts?.signal
2619
+ });
2620
+ },
2621
+ delete: (siteUrl, opts) => fetch(`${GSC_API}/webmasters/v3/sites/${encodeSiteUrl(siteUrl)}`, {
2482
2622
  method: "DELETE",
2483
2623
  signal: opts?.signal
2484
2624
  })
@@ -2507,7 +2647,11 @@ function googleSearchConsole(auth, options = {}) {
2507
2647
  },
2508
2648
  inspect: (siteUrl, url, opts) => fetch(`${GSC_API}/v1/urlInspection/index:inspect`, {
2509
2649
  method: "POST",
2510
- body: {
2650
+ body: opts?.languageCode ? {
2651
+ inspectionUrl: url,
2652
+ siteUrl,
2653
+ languageCode: opts.languageCode
2654
+ } : {
2511
2655
  inspectionUrl: url,
2512
2656
  siteUrl
2513
2657
  },
@@ -2515,14 +2659,17 @@ function googleSearchConsole(auth, options = {}) {
2515
2659
  }),
2516
2660
  sitemaps: {
2517
2661
  list: async (siteUrl, opts) => {
2518
- return (await fetch(`${GSC_API}/webmasters/v3/sites/${encodeURIComponent(siteUrl)}/sitemaps`, { signal: opts?.signal })).sitemap || [];
2662
+ return (await fetch(`${GSC_API}/webmasters/v3/sites/${encodeSiteUrl(siteUrl)}/sitemaps`, {
2663
+ signal: opts?.signal,
2664
+ query: opts?.sitemapIndex ? { sitemapIndex: opts.sitemapIndex } : void 0
2665
+ })).sitemap || [];
2519
2666
  },
2520
- get: (siteUrl, feedpath, opts) => fetch(`${GSC_API}/webmasters/v3/sites/${encodeURIComponent(siteUrl)}/sitemaps/${encodeURIComponent(feedpath)}`, { signal: opts?.signal }),
2521
- submit: (siteUrl, feedpath, opts) => fetch(`${GSC_API}/webmasters/v3/sites/${encodeURIComponent(siteUrl)}/sitemaps/${encodeURIComponent(feedpath)}`, {
2667
+ get: (siteUrl, feedpath, opts) => fetch(`${GSC_API}/webmasters/v3/sites/${encodeSiteUrl(siteUrl)}/sitemaps/${encodeURIComponent(feedpath)}`, { signal: opts?.signal }),
2668
+ submit: (siteUrl, feedpath, opts) => fetch(`${GSC_API}/webmasters/v3/sites/${encodeSiteUrl(siteUrl)}/sitemaps/${encodeURIComponent(feedpath)}`, {
2522
2669
  method: "PUT",
2523
2670
  signal: opts?.signal
2524
2671
  }),
2525
- delete: (siteUrl, feedpath, opts) => fetch(`${GSC_API}/webmasters/v3/sites/${encodeURIComponent(siteUrl)}/sitemaps/${encodeURIComponent(feedpath)}`, {
2672
+ delete: (siteUrl, feedpath, opts) => fetch(`${GSC_API}/webmasters/v3/sites/${encodeSiteUrl(siteUrl)}/sitemaps/${encodeURIComponent(feedpath)}`, {
2526
2673
  method: "DELETE",
2527
2674
  signal: opts?.signal
2528
2675
  })
@@ -2698,8 +2845,8 @@ function findExactGscProperty(propertyUrl, properties) {
2698
2845
  function formatGscPropertyCandidates(candidates) {
2699
2846
  return candidates.filter((c) => !!c).map((property) => `${property.siteUrl} (${property.permissionLevel})`).join(", ");
2700
2847
  }
2701
- const INDEXING_DAILY_LIMIT = 2e3;
2702
- const INDEXING_EFFECTIVE_LIMIT = 1800;
2848
+ const URL_INSPECTION_DAILY_LIMIT = 2e3;
2849
+ const URL_INSPECTION_EFFECTIVE_LIMIT = 1800;
2703
2850
  const GSCDUMP_ONBOARDING_CONTRACT_VERSION = "2026-05-11";
2704
2851
  const GSCDUMP_REQUIRED_ANALYTICS_SCOPE = "https://www.googleapis.com/auth/webmasters.readonly";
2705
2852
  const GSCDUMP_OPTIONAL_INDEXING_SCOPE = "https://www.googleapis.com/auth/indexing";
@@ -2888,4 +3035,4 @@ async function fetchSitemapUrls(sitemapUrl, options = {}) {
2888
3035
  await visit(sitemapUrl, 0);
2889
3036
  return out;
2890
3037
  }
2891
- export { DAYS_PER_RANGE, GSCDUMP_ONBOARDING_CONTRACT_VERSION, GSCDUMP_OPTIONAL_INDEXING_SCOPE, GSCDUMP_REQUIRED_ANALYTICS_SCOPE, GSC_FINALIZED_LAG_DAYS, GSC_FRESHEST_LAG_DAYS, GSC_QUOTAS, GSC_RETENTION_MONTHS, GscApiError, INDEXING_DAILY_LIMIT, INDEXING_EFFECTIVE_LIMIT, INDEXING_ISSUE_FILTERS, INDEXING_ISSUE_LABELS, INDEXING_ISSUE_SEVERITY, MS_PER_DAY, accountNextActions, accountStatuses, addDays, addSite, analyticsNextActions, analyticsStatuses, batchInspectUrls, batchRequestIndexing, canUseUrlInspection, classifyError, countDays, createAuth, createFetch, daysAgo, deleteSite, deleteSitemap, discoverSitemap, exchangeAuthCode, fetchSitemap, fetchSitemapUrls, fetchSitemaps, fetchSites, fetchSitesWithSitemaps, findBestGscProperty, findExactGscProperty, formatErrorForCli, formatGscPropertyCandidates, generateGscDateRange, getBackfillProgress, getDateRange, getFreshestGscDate, getIndexingEligibility, getIndexingMetadata, getLatestGscDate, getNextCheckAfter, getNextCheckPriority, getNextDate, getOldestGscDate, getPendingDates, getPreviousDate, getPstDate, getVerificationToken, getVerifiedSite, googleSearchConsole, grantedScopeList, groupIntoRanges, gscPropertyMatchesTarget, gscdumpApi, hasGscReadScope, hasGscWriteScope, hasIndexingScope, hasOptionalIndexingScope, hasRequiredAnalyticsScope, indexingNextActions, indexingStatuses, inspectUrl, inspectUrlFlat, isPermissionDeniedError, isValidGscDate, isVerifiedGscPermission, isVerifiedGscProperty, lifecycleErrorCodes, lifecycleWebhookEvents, listVerifiedSites, matchGscSite, normalizeGscSiteUrl, normalizeRegistrationTarget, parseGoogleError, parseGrantedScopes, parseGscSiteUrl, pickBestGscProperty, progressBar, propertyNextActions, propertyStatuses, querySourceModes, refreshAccessToken, requestIndexing, rethrowAsGscApiError, rowWithMetricDefaults, runSequentialBatch, siteUrlToVerificationSite, sitemapNextActions, sitemapStatuses, storageError, submitSitemap, toIsoDate, unverifySite, verificationMethodsFor, verifySite };
3038
+ export { DAYS_PER_RANGE, GSCDUMP_ONBOARDING_CONTRACT_VERSION, GSCDUMP_OPTIONAL_INDEXING_SCOPE, GSCDUMP_REQUIRED_ANALYTICS_SCOPE, GSC_FINALIZED_LAG_DAYS, GSC_FRESHEST_LAG_DAYS, GSC_QUOTAS, GSC_RETENTION_MONTHS, GscApiError, INDEXING_ISSUE_FILTERS, INDEXING_ISSUE_LABELS, INDEXING_ISSUE_SEVERITY, MS_PER_DAY, URL_INSPECTION_DAILY_LIMIT, URL_INSPECTION_EFFECTIVE_LIMIT, accountNextActions, accountStatuses, addDays, addSite, analyticsNextActions, analyticsStatuses, batchInspectUrls, batchRequestIndexing, canUseUrlInspection, classifyError, countDays, createAuth, createFetch, daysAgo, deleteSite, deleteSitemap, discoverSitemap, exchangeAuthCode, fetchSitemap, fetchSitemapUrls, fetchSitemaps, fetchSites, fetchSitesWithSitemaps, findBestGscProperty, findExactGscProperty, formatErrorForCli, formatGscPropertyCandidates, generateGscDateRange, getBackfillProgress, getDateRange, getFreshestGscDate, getIndexingEligibility, getIndexingMetadata, getLatestGscDate, getNextCheckAfter, getNextCheckPriority, getNextDate, getOldestGscDate, getPendingDates, getPreviousDate, getPstDate, getVerificationToken, getVerifiedSite, googleSearchConsole, grantedScopeList, groupIntoRanges, gscPropertyMatchesTarget, gscdumpApi, hasGscReadScope, hasGscWriteScope, hasIndexingScope, hasOptionalIndexingScope, hasRequiredAnalyticsScope, indexingNextActions, indexingStatuses, inspectUrl, inspectUrlFlat, isPermissionDeniedError, isValidGscDate, isVerifiedGscPermission, isVerifiedGscProperty, lifecycleErrorCodes, lifecycleWebhookEvents, listVerifiedSites, matchGscSite, normalizeGscSiteUrl, normalizeRegistrationTarget, parseGoogleError, parseGrantedScopes, parseGscSiteUrl, pickBestGscProperty, progressBar, propertyNextActions, propertyStatuses, querySourceModes, refreshAccessToken, requestIndexing, rethrowAsGscApiError, rowWithMetricDefaults, runSequentialBatch, siteUrlToVerificationSite, sitemapNextActions, sitemapStatuses, storageError, submitSitemap, toIsoDate, unverifySite, verificationMethodsFor, verifySite };
@@ -1,6 +1,7 @@
1
1
  import _dayjs, { Dayjs } from "dayjs";
2
2
  type TableName = 'pages' | 'keywords' | 'countries' | 'devices' | 'page_keywords' | 'search_appearance';
3
- type GscSearchAnalyticsDimension = 'page' | 'query' | 'country' | 'device' | 'date' | 'searchAppearance';
3
+ type GscSearchAnalyticsDimension = 'page' | 'query' | 'country' | 'device' | 'date' | 'hour' | 'searchAppearance';
4
+ type GscDataState = 'final' | 'all' | 'hourly_all';
4
5
  type GscSearchAnalyticsFilterOperator = 'equals' | 'notEquals' | 'contains' | 'notContains' | 'includingRegex' | 'excludingRegex';
5
6
  interface GscSearchAnalyticsFilter {
6
7
  dimension: GscSearchAnalyticsDimension;
@@ -12,6 +13,7 @@ interface GscSearchAnalyticsFilterGroup {
12
13
  filters: GscSearchAnalyticsFilter[];
13
14
  }
14
15
  type GscSearchType = 'web' | 'image' | 'video' | 'news' | 'discover' | 'googleNews';
16
+ type GscAggregationType = 'auto' | 'byPage' | 'byProperty' | 'byNewsShowcasePanel';
15
17
  interface GscSearchAnalyticsRequest {
16
18
  startDate: string;
17
19
  endDate: string;
@@ -19,7 +21,10 @@ interface GscSearchAnalyticsRequest {
19
21
  dimensionFilterGroups?: GscSearchAnalyticsFilterGroup[];
20
22
  rowLimit?: number;
21
23
  startRow?: number;
22
- searchType?: GscSearchType;
24
+ /** GSC search corpus. Maps to wire field `type` (the API still accepts the deprecated `searchType` alias). */
25
+ type?: GscSearchType;
26
+ dataState?: GscDataState;
27
+ aggregationType?: GscAggregationType;
23
28
  }
24
29
  declare const _default: {
25
30
  name: string;
@@ -52,6 +57,8 @@ interface DimensionValueMap {
52
57
  device: Device;
53
58
  searchAppearance: string;
54
59
  date: string;
60
+ /** Hour bucket — ISO-8601 with PT offset, e.g. `2025-07-14T13:00:00-07:00`. Use with `dataState: 'hourly_all'`. */
61
+ hour: string;
55
62
  }
56
63
  type Dimension = keyof DimensionValueMap;
57
64
  interface QueryParamValueMap {
@@ -110,6 +117,12 @@ interface BuilderState {
110
117
  };
111
118
  rowLimit?: number;
112
119
  startRow?: number;
120
+ /** GSC `dataState`. `'hourly_all'` is required when grouping by `hour`. */
121
+ dataState?: GscDataState;
122
+ /** GSC `aggregationType`. `'byNewsShowcasePanel'` requires `type=discover|googleNews` with NEWS_SHOWCASE searchAppearance. */
123
+ aggregationType?: GscAggregationType;
124
+ /** GSC search corpus. Wins over any `searchType` filter when both are set. */
125
+ searchType?: SearchType;
113
126
  }
114
127
  interface JsonInternalFilter {
115
128
  dimension: string;
@@ -136,6 +149,10 @@ interface GSCQueryBuilder<D extends Dimension[] = [], C = object> {
136
149
  orderBy: (col: OrderableColumn, dir: 'asc' | 'desc') => GSCQueryBuilder<D, C>;
137
150
  limit: (n: number) => GSCQueryBuilder<D, C>;
138
151
  offset: (n: number) => GSCQueryBuilder<D, C>;
152
+ dataState: (state: GscDataState) => GSCQueryBuilder<D, C>;
153
+ aggregationType: (type: GscAggregationType) => GSCQueryBuilder<D, C>;
154
+ /** GSC search corpus (`type` on the wire). Equivalent to filtering by `searchType`. */
155
+ type: (t: GscSearchType) => GSCQueryBuilder<D, C>;
139
156
  toBody: () => GscSearchAnalyticsRequest;
140
157
  getState: () => BuilderState;
141
158
  }
@@ -147,6 +164,7 @@ declare const device: Column<"device">;
147
164
  declare const country: Column<"country">;
148
165
  declare const searchAppearance: Column<"searchAppearance">;
149
166
  declare const date: Column<"date">;
167
+ declare const hour: Column<"hour">;
150
168
  declare const clicks: MetricColumn<"clicks">;
151
169
  declare const impressions: MetricColumn<"impressions">;
152
170
  declare const ctr: MetricColumn<"ctr">;
@@ -164,15 +182,15 @@ declare function and<F extends Filter<any>[]>(...filters: F): Filter<MergeConstr
164
182
  declare function or<F extends Filter<any>[]>(...filters: F): Filter<object>;
165
183
  declare function not<F extends Filter<any>>(filter: F): Filter<object>;
166
184
  declare function gte<M extends Metric>(column: MetricColumn<M>, value: number): Filter<object>;
167
- declare function gte<D extends Dimension>(column: Column<D>, value: DimensionValueMap[D]): Filter<object>;
185
+ declare function gte(column: Column<'date'>, value: string): Filter<object>;
168
186
  declare function gt<M extends Metric>(column: MetricColumn<M>, value: number): Filter<object>;
169
- declare function gt<D extends Dimension>(column: Column<D>, value: DimensionValueMap[D]): Filter<object>;
187
+ declare function gt(column: Column<'date'>, value: string): Filter<object>;
170
188
  declare function lte<M extends Metric>(column: MetricColumn<M>, value: number): Filter<object>;
171
- declare function lte<D extends Dimension>(column: Column<D>, value: DimensionValueMap[D]): Filter<object>;
189
+ declare function lte(column: Column<'date'>, value: string): Filter<object>;
172
190
  declare function lt<M extends Metric>(column: MetricColumn<M>, value: number): Filter<object>;
173
- declare function lt<D extends Dimension>(column: Column<D>, value: DimensionValueMap[D]): Filter<object>;
191
+ declare function lt(column: Column<'date'>, value: string): Filter<object>;
174
192
  declare function between<M extends Metric>(column: MetricColumn<M>, start: number, end: number): Filter<object>;
175
- declare function between<D extends Dimension>(column: Column<D>, start: DimensionValueMap[D], end: DimensionValueMap[D]): Filter<object>;
193
+ declare function between(column: Column<'date'>, start: string, end: string): Filter<object>;
176
194
  declare function topLevel(column: Column<'page'>): Filter<object>;
177
195
  type LogicalDataset = TableName;
178
196
  type ComparisonFilter = 'new' | 'lost' | 'improving' | 'declining';
@@ -258,4 +276,4 @@ declare function currentPstDate(): string;
258
276
  declare function dayjsPst(): Dayjs;
259
277
  declare function today(): string;
260
278
  declare function daysAgo(n: number): string;
261
- export { type BuilderState, type Column, type ComparisonFilter, Countries, type Country, type Device, Devices, type Dimension, type DimensionValueMap, type Filter, type FilterInput, type GSCQueryBuilder, type GSCResult, type GSCRow, type InternalFilter, type JsonFilter, type JsonInternalFilter, type LogicalComparisonPlan, type LogicalDataset, type LogicalDimensionFilter, type LogicalMetricFilter, type LogicalQueryPlan, type Metric, type MetricColumn, type PlannerCapabilities, type QueryParam, type QueryParamName, type QueryParamValueMap, type SearchType, SearchTypes, UnsupportedLogicalCapabilityError, and, between, buildLogicalComparisonPlan, buildLogicalPlan, clicks, contains, country, ctr, currentPstDate, date, dayjs, dayjsPst, daysAgo, device, eq, extractDateRange, extractMetricFilters, extractSearchType, extractSpecialOperatorFilters, gsc, gt, gte, impressions, inArray, isJsonFilter, like, lt, lte, ne, normalizeBuilderState, normalizeFilter, not, notRegex, or, page, parseJsonFilter, position, query, queryCanonical, regex, searchAppearance, searchType, today, topLevel };
279
+ export { type BuilderState, type Column, type ComparisonFilter, Countries, type Country, type Device, Devices, type Dimension, type DimensionValueMap, type Filter, type FilterInput, type GSCQueryBuilder, type GSCResult, type GSCRow, type InternalFilter, type JsonFilter, type JsonInternalFilter, type LogicalComparisonPlan, type LogicalDataset, type LogicalDimensionFilter, type LogicalMetricFilter, type LogicalQueryPlan, type Metric, type MetricColumn, type PlannerCapabilities, type QueryParam, type QueryParamName, type QueryParamValueMap, type SearchType, SearchTypes, UnsupportedLogicalCapabilityError, and, between, buildLogicalComparisonPlan, buildLogicalPlan, clicks, contains, country, ctr, currentPstDate, date, dayjs, dayjsPst, daysAgo, device, eq, extractDateRange, extractMetricFilters, extractSearchType, extractSpecialOperatorFilters, gsc, gt, gte, hour, impressions, inArray, isJsonFilter, like, lt, lte, ne, normalizeBuilderState, normalizeFilter, not, notRegex, or, page, parseJsonFilter, position, query, queryCanonical, regex, searchAppearance, searchType, today, topLevel };
@@ -1640,7 +1640,9 @@ function normalizeBuilderState(state) {
1640
1640
  filter: normalizeFilter(s.filter),
1641
1641
  orderBy: s.orderBy,
1642
1642
  rowLimit: s.rowLimit,
1643
- startRow: s.startRow
1643
+ startRow: s.startRow,
1644
+ dataState: s.dataState,
1645
+ aggregationType: s.aggregationType
1644
1646
  };
1645
1647
  }
1646
1648
  function extractSpecialFilters(filter) {
@@ -1674,9 +1676,9 @@ function extractSpecialFilters(filter) {
1674
1676
  else otherFilters.push(f);
1675
1677
  if (filter._nestedGroups) for (const nested of filter._nestedGroups) {
1676
1678
  const extracted = extractSpecialFilters(nested);
1677
- if (extracted.startDate) startDate = extracted.startDate;
1678
- if (extracted.endDate) endDate = extracted.endDate;
1679
- if (extracted.searchType) searchType = extracted.searchType;
1679
+ if (!startDate && extracted.startDate) startDate = extracted.startDate;
1680
+ if (!endDate && extracted.endDate) endDate = extracted.endDate;
1681
+ if (!searchType && extracted.searchType) searchType = extracted.searchType;
1680
1682
  if (extracted.dimensionFilter) cleanedNestedGroups.push(extracted.dimensionFilter);
1681
1683
  }
1682
1684
  const dimensionFilter = otherFilters.length > 0 || cleanedNestedGroups.length > 0 ? {
@@ -1714,6 +1716,7 @@ function extractSpecialOperatorFilters(input) {
1714
1716
  }
1715
1717
  function extractSearchType(state) {
1716
1718
  if (!state) return void 0;
1719
+ if (state.searchType && KNOWN_SEARCH_TYPES.has(state.searchType)) return state.searchType;
1717
1720
  const filter = state.filter;
1718
1721
  if (!filter || typeof filter !== "object") return void 0;
1719
1722
  const raw = filter.searchType;
@@ -1728,11 +1731,40 @@ function resolveToBody(state) {
1728
1731
  startDate,
1729
1732
  endDate
1730
1733
  };
1731
- if (searchType) body.searchType = searchType;
1732
- if (state.rowLimit) body.rowLimit = state.rowLimit;
1733
- if (state.startRow) body.startRow = state.startRow;
1734
+ const resolvedType = state.searchType ?? searchType;
1735
+ if (resolvedType) body.type = resolvedType;
1736
+ if (state.rowLimit !== void 0) {
1737
+ if (!Number.isInteger(state.rowLimit) || state.rowLimit < 1) throw new Error(`rowLimit must be a positive integer, got ${state.rowLimit}`);
1738
+ body.rowLimit = state.rowLimit;
1739
+ }
1740
+ if (state.startRow !== void 0) {
1741
+ if (!Number.isInteger(state.startRow) || state.startRow < 0) throw new Error(`startRow must be a non-negative integer, got ${state.startRow}`);
1742
+ if (state.startRow > 0) body.startRow = state.startRow;
1743
+ }
1744
+ const hasHour = state.dimensions?.includes("hour");
1745
+ if (hasHour && state.dataState !== "hourly_all") throw new Error("hour dimension requires dataState: \"hourly_all\"");
1746
+ if (state.dataState === "hourly_all" && !hasHour) throw new Error("dataState: \"hourly_all\" requires grouping by hour dimension");
1747
+ if (state.dataState) body.dataState = state.dataState;
1734
1748
  const filterGroups = resolveFilter(dimensionFilter);
1735
1749
  if (filterGroups.length > 0) body.dimensionFilterGroups = filterGroups;
1750
+ if (state.aggregationType) {
1751
+ const groupsByPage = (body.dimensions ?? []).includes("page");
1752
+ const apiLeafFilters = filterGroups.flatMap((g) => g.filters ?? []);
1753
+ const filtersByPage = apiLeafFilters.some((f) => f.dimension === "page");
1754
+ if (state.aggregationType === "byProperty") {
1755
+ if (body.type === "discover" || body.type === "googleNews") throw new Error("aggregationType: \"byProperty\" is not supported for type \"discover\" or \"googleNews\"");
1756
+ if (groupsByPage || filtersByPage) throw new Error("aggregationType: \"byProperty\" is not allowed when grouping or filtering by page");
1757
+ }
1758
+ if (state.aggregationType === "byNewsShowcasePanel") {
1759
+ if (body.type !== "discover" && body.type !== "googleNews") throw new Error("aggregationType: \"byNewsShowcasePanel\" requires type \"discover\" or \"googleNews\"");
1760
+ if (groupsByPage || filtersByPage) throw new Error("aggregationType: \"byNewsShowcasePanel\" is not allowed when grouping or filtering by page");
1761
+ const saFilters = apiLeafFilters.filter((f) => f.dimension === "searchAppearance");
1762
+ const hasNewsShowcase = saFilters.some((f) => f.operator === "equals" && f.expression === "NEWS_SHOWCASE");
1763
+ const hasOther = saFilters.some((f) => !(f.operator === "equals" && f.expression === "NEWS_SHOWCASE"));
1764
+ if (!hasNewsShowcase || hasOther) throw new Error("aggregationType: \"byNewsShowcasePanel\" requires a searchAppearance equals \"NEWS_SHOWCASE\" filter and no other searchAppearance filter");
1765
+ }
1766
+ body.aggregationType = state.aggregationType;
1767
+ }
1736
1768
  return body;
1737
1769
  }
1738
1770
  function isApiFilter(f) {
@@ -1817,6 +1849,24 @@ function createBuilder(state) {
1817
1849
  startRow: n
1818
1850
  });
1819
1851
  },
1852
+ dataState(s) {
1853
+ return createBuilder({
1854
+ ...state,
1855
+ dataState: s
1856
+ });
1857
+ },
1858
+ aggregationType(t) {
1859
+ return createBuilder({
1860
+ ...state,
1861
+ aggregationType: t
1862
+ });
1863
+ },
1864
+ type(t) {
1865
+ return createBuilder({
1866
+ ...state,
1867
+ searchType: t
1868
+ });
1869
+ },
1820
1870
  toBody() {
1821
1871
  return resolveToBody(state);
1822
1872
  },
@@ -1842,6 +1892,7 @@ const device = createColumn("device");
1842
1892
  const country = createColumn("country");
1843
1893
  const searchAppearance = createColumn("searchAppearance");
1844
1894
  const date = createColumn("date");
1895
+ const hour = createColumn("hour");
1845
1896
  const clicks = createMetricColumn("clicks");
1846
1897
  const impressions = createMetricColumn("impressions");
1847
1898
  const ctr = createMetricColumn("ctr");
@@ -1869,6 +1920,7 @@ function ne(column, value) {
1869
1920
  return leafFilter(column.dimension, "notEquals", String(value));
1870
1921
  }
1871
1922
  function inArray(column, values) {
1923
+ if (values.length === 0) throw new Error(`inArray(${column.dimension}, []) requires at least one value`);
1872
1924
  return {
1873
1925
  _constraints: {},
1874
1926
  _filters: values.map((v) => ({
@@ -1883,7 +1935,9 @@ function contains(column, pattern) {
1883
1935
  return leafFilter(column.dimension, "contains", pattern);
1884
1936
  }
1885
1937
  function like(column, pattern) {
1886
- return leafFilter(column.dimension, "contains", pattern.replace(/%/g, ""));
1938
+ if (!/[%_]/.test(pattern)) return leafFilter(column.dimension, "contains", pattern);
1939
+ const regex = pattern.replace(/[.*+?^${}()|[\]\\]/g, "\\$&").replace(/%/g, ".*").replace(/_/g, ".");
1940
+ return leafFilter(column.dimension, "includingRegex", regex);
1887
1941
  }
1888
1942
  function regex(column, pattern) {
1889
1943
  return leafFilter(column.dimension, "includingRegex", typeof pattern === "string" ? pattern : pattern.source);
@@ -1907,30 +1961,45 @@ function and(...filters) {
1907
1961
  };
1908
1962
  }
1909
1963
  function or(...filters) {
1964
+ for (const f of filters) {
1965
+ if (f._groupType === "and" && f._filters.length > 1) throw new Error("or() cannot contain a multi-leaf AND group: GSC filter groups do not nest. Restructure as flat OR or split into multiple queries.");
1966
+ if (f._nestedGroups && f._nestedGroups.length > 0) throw new Error("or() cannot contain nested filter groups: GSC filter groups do not nest.");
1967
+ for (const leaf of f._filters) {
1968
+ if (leaf.dimension === "date") throw new Error("or() cannot contain a date filter: GSC date range is a top-level AND-applied request field, not a filter. Apply the date range outside the or() group.");
1969
+ if (leaf.dimension === "searchType") throw new Error("or() cannot contain a searchType filter: GSC search type is a top-level AND-applied request field. Use .type() outside the or() group.");
1970
+ }
1971
+ }
1910
1972
  return {
1911
1973
  _constraints: {},
1912
1974
  _filters: filters.flatMap((f) => f._filters),
1913
1975
  _groupType: "or"
1914
1976
  };
1915
1977
  }
1978
+ const INVERSIONS = {
1979
+ equals: "notEquals",
1980
+ notEquals: "equals",
1981
+ contains: "notContains",
1982
+ notContains: "contains",
1983
+ includingRegex: "excludingRegex",
1984
+ excludingRegex: "includingRegex"
1985
+ };
1986
+ function invertOperator(op) {
1987
+ return INVERSIONS[op];
1988
+ }
1916
1989
  function not(filter) {
1917
- return {
1918
- _constraints: {},
1919
- _filters: filter._filters.filter((f) => !DATE_OPERATORS.includes(f.operator)).map((f) => ({
1990
+ const inverted = [];
1991
+ for (const f of filter._filters) {
1992
+ if (DATE_OPERATORS.includes(f.operator)) throw new Error(`not() cannot invert date operator "${f.operator}": GSC has no negated date filter.`);
1993
+ if (!(f.operator in INVERSIONS)) throw new Error(`not() cannot invert operator "${f.operator}".`);
1994
+ inverted.push({
1920
1995
  ...f,
1921
1996
  operator: invertOperator(f.operator)
1922
- }))
1923
- };
1924
- }
1925
- function invertOperator(op) {
1997
+ });
1998
+ }
1926
1999
  return {
1927
- equals: "notEquals",
1928
- notEquals: "equals",
1929
- contains: "notContains",
1930
- notContains: "contains",
1931
- includingRegex: "excludingRegex",
1932
- excludingRegex: "includingRegex"
1933
- }[op];
2000
+ _constraints: {},
2001
+ _filters: inverted
2002
+ };
1934
2003
  }
1935
2004
  function gte(column, value) {
1936
2005
  return metricOrDimFilter(column, "metricGte", "gte", String(value));
@@ -2097,4 +2166,4 @@ function today() {
2097
2166
  function daysAgo(n) {
2098
2167
  return dayjsPst().subtract(n, "day").format("YYYY-MM-DD");
2099
2168
  }
2100
- export { Countries, Devices, SearchTypes, UnsupportedLogicalCapabilityError, and, between, buildLogicalComparisonPlan, buildLogicalPlan, clicks, contains, country, ctr, currentPstDate, date, dayjs, dayjsPst, daysAgo, device, eq, extractDateRange, extractMetricFilters, extractSearchType, extractSpecialOperatorFilters, gsc, gt, gte, impressions, inArray, isJsonFilter, like, lt, lte, ne, normalizeBuilderState, normalizeFilter, not, notRegex, or, page, parseJsonFilter, position, query, queryCanonical, regex, searchAppearance, searchType, today, topLevel };
2169
+ export { Countries, Devices, SearchTypes, UnsupportedLogicalCapabilityError, and, between, buildLogicalComparisonPlan, buildLogicalPlan, clicks, contains, country, ctr, currentPstDate, date, dayjs, dayjsPst, daysAgo, device, eq, extractDateRange, extractMetricFilters, extractSearchType, extractSpecialOperatorFilters, gsc, gt, gte, hour, impressions, inArray, isJsonFilter, like, lt, lte, ne, normalizeBuilderState, normalizeFilter, not, notRegex, or, page, parseJsonFilter, position, query, queryCanonical, regex, searchAppearance, searchType, today, topLevel };
@@ -1,4 +1,6 @@
1
1
  type TableName = 'pages' | 'keywords' | 'countries' | 'devices' | 'page_keywords' | 'search_appearance';
2
+ type GscDataState = 'final' | 'all' | 'hourly_all';
3
+ type GscAggregationType = 'auto' | 'byPage' | 'byProperty' | 'byNewsShowcasePanel';
2
4
  declare const _default: {
3
5
  name: string;
4
6
  'alpha-2': string;
@@ -30,6 +32,8 @@ interface DimensionValueMap {
30
32
  device: Device;
31
33
  searchAppearance: string;
32
34
  date: string;
35
+ /** Hour bucket — ISO-8601 with PT offset, e.g. `2025-07-14T13:00:00-07:00`. Use with `dataState: 'hourly_all'`. */
36
+ hour: string;
33
37
  }
34
38
  type Dimension = keyof DimensionValueMap;
35
39
  interface QueryParamValueMap {
@@ -65,6 +69,12 @@ interface BuilderState {
65
69
  };
66
70
  rowLimit?: number;
67
71
  startRow?: number;
72
+ /** GSC `dataState`. `'hourly_all'` is required when grouping by `hour`. */
73
+ dataState?: GscDataState;
74
+ /** GSC `aggregationType`. `'byNewsShowcasePanel'` requires `type=discover|googleNews` with NEWS_SHOWCASE searchAppearance. */
75
+ aggregationType?: GscAggregationType;
76
+ /** GSC search corpus. Wins over any `searchType` filter when both are set. */
77
+ searchType?: SearchType;
68
78
  }
69
79
  type LogicalDataset = TableName;
70
80
  type ComparisonFilter = 'new' | 'lost' | 'improving' | 'declining';
@@ -1627,9 +1627,9 @@ function extractSpecialFilters(filter) {
1627
1627
  else otherFilters.push(f);
1628
1628
  if (filter._nestedGroups) for (const nested of filter._nestedGroups) {
1629
1629
  const extracted = extractSpecialFilters(nested);
1630
- if (extracted.startDate) startDate = extracted.startDate;
1631
- if (extracted.endDate) endDate = extracted.endDate;
1632
- if (extracted.searchType) searchType = extracted.searchType;
1630
+ if (!startDate && extracted.startDate) startDate = extracted.startDate;
1631
+ if (!endDate && extracted.endDate) endDate = extracted.endDate;
1632
+ if (!searchType && extracted.searchType) searchType = extracted.searchType;
1633
1633
  if (extracted.dimensionFilter) cleanedNestedGroups.push(extracted.dimensionFilter);
1634
1634
  }
1635
1635
  const dimensionFilter = otherFilters.length > 0 || cleanedNestedGroups.length > 0 ? {
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "gscdump",
3
3
  "type": "module",
4
- "version": "0.17.5",
4
+ "version": "0.18.1",
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",
@@ -108,7 +108,7 @@
108
108
  "defu": "^6.1.7",
109
109
  "ofetch": "^1.5.1",
110
110
  "ufo": "^1.6.4",
111
- "@gscdump/contracts": "0.17.5"
111
+ "@gscdump/contracts": "0.18.1"
112
112
  },
113
113
  "devDependencies": {
114
114
  "@googleapis/indexing": "^6.0.1",