gscdump 0.25.13 → 0.26.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.
@@ -0,0 +1,29 @@
1
+ interface Ok<A> {
2
+ readonly ok: true;
3
+ readonly value: A;
4
+ }
5
+ interface Err<E> {
6
+ readonly ok: false;
7
+ readonly error: E;
8
+ }
9
+ type Result<A, E> = Ok<A> | Err<E>;
10
+ declare function ok<A>(value: A): Ok<A>;
11
+ declare function err<E>(error: E): Err<E>;
12
+ declare function isOk<A, E>(result: Result<A, E>): result is Ok<A>;
13
+ declare function isErr<A, E>(result: Result<A, E>): result is Err<E>;
14
+ /** Maps the success channel, leaving an `Err` untouched. */
15
+ declare function mapResult<A, B, E>(result: Result<A, E>, f: (value: A) => B): Result<B, E>;
16
+ /** Maps the error channel, leaving an `Ok` untouched. */
17
+ declare function mapErr<A, E, F>(result: Result<A, E>, f: (error: E) => F): Result<A, F>;
18
+ /** Folds both channels into a single value. */
19
+ declare function matchResult<A, E, R>(result: Result<A, E>, handlers: {
20
+ onOk: (value: A) => R;
21
+ onErr: (error: E) => R;
22
+ }): R;
23
+ /**
24
+ * Collapses a `Result` back into the throwing world: returns the value or throws
25
+ * via `toError`. Used by the throwing wrappers that sit over the `Result` core so
26
+ * existing call sites keep their `await`/`throw` ergonomics.
27
+ */
28
+ declare function unwrapResult<A, E>(result: Result<A, E>, toError: (error: E) => unknown): A;
29
+ export { Err, Ok, Result, err, isErr, isOk, mapErr, mapResult, matchResult, ok, unwrapResult };
@@ -0,0 +1,32 @@
1
+ function ok(value) {
2
+ return {
3
+ ok: true,
4
+ value
5
+ };
6
+ }
7
+ function err(error) {
8
+ return {
9
+ ok: false,
10
+ error
11
+ };
12
+ }
13
+ function isOk(result) {
14
+ return result.ok;
15
+ }
16
+ function isErr(result) {
17
+ return !result.ok;
18
+ }
19
+ function mapResult(result, f) {
20
+ return result.ok ? ok(f(result.value)) : result;
21
+ }
22
+ function mapErr(result, f) {
23
+ return result.ok ? result : err(f(result.error));
24
+ }
25
+ function matchResult(result, handlers) {
26
+ return result.ok ? handlers.onOk(result.value) : handlers.onErr(result.error);
27
+ }
28
+ function unwrapResult(result, toError) {
29
+ if (result.ok) return result.value;
30
+ throw toError(result.error);
31
+ }
32
+ export { err, isErr, isOk, mapErr, mapResult, matchResult, ok, unwrapResult };
package/dist/index.d.mts CHANGED
@@ -423,17 +423,134 @@ interface IndexingEligibility {
423
423
  grantedScopes?: string[];
424
424
  }
425
425
  declare function getIndexingEligibility(grantedScopes: string | null | undefined, permissionLevel: string | null | undefined): IndexingEligibility;
426
+ type GscErrorKind = 'auth-expired' | 'rate-limited' | 'not-found' | 'validation' | 'storage' | 'transport';
427
+ type GscError = {
428
+ kind: 'auth-expired';
429
+ message: string;
430
+ cause: unknown;
431
+ } | {
432
+ kind: 'rate-limited';
433
+ message: string;
434
+ retryAfter?: number;
435
+ cause: unknown;
436
+ } | {
437
+ kind: 'not-found';
438
+ message: string;
439
+ cause: unknown;
440
+ } | {
441
+ kind: 'validation';
442
+ message: string;
443
+ cause: unknown;
444
+ } | {
445
+ kind: 'storage';
446
+ message: string;
447
+ cause: unknown;
448
+ } | {
449
+ kind: 'transport';
450
+ message: string;
451
+ status?: number;
452
+ cause: unknown;
453
+ };
454
+ /** Approximate per-day GSC API quotas, used in CLI messaging. */
455
+ declare const GSC_QUOTAS: {
456
+ readonly searchAnalytics: 25000;
457
+ readonly urlInspection: 2000;
458
+ readonly indexing: 200;
459
+ };
460
+ /**
461
+ * Classify an unknown error into a `GscError` discriminated union.
462
+ * Transport is the catch-all — anything without a recognizable status ends up there.
463
+ */
464
+ declare function classifyError(cause: unknown): GscError;
465
+ /** Construct a storage-kind error from inside the analytics engine / adapters. */
466
+ declare function storageError(message: string, cause?: unknown): GscError;
467
+ /**
468
+ * Re-raises a `GscError` value as an `Error`, preserving its `cause` for
469
+ * stack-walking and stashing the original union under `.gscError`. Pairs with
470
+ * `unwrapResult` from `gscdump/result` so a `fooResult(): Result<A, GscError>`
471
+ * core can be wrapped by a throwing `foo()` without losing the typed error.
472
+ */
473
+ declare function gscErrorToException(error: GscError): Error;
474
+ interface GscApiErrorInfo {
475
+ code: number;
476
+ message: string;
477
+ reason?: string;
478
+ status?: string;
479
+ }
480
+ declare function parseGoogleError(text: string, httpStatus?: number): GscApiErrorInfo;
481
+ declare class GscApiError extends Error {
482
+ info: GscApiErrorInfo;
483
+ constructor(message: string, info: GscApiErrorInfo);
484
+ }
485
+ /**
486
+ * Returns a handler that re-throws `unknown` as a `GscApiError` prefixed with
487
+ * `prefix`. Recognises `ofetch` `FetchError` (parses `err.data` as Google JSON
488
+ * via `parseGoogleError`); passes through existing `GscApiError`; re-throws
489
+ * anything else as-is.
490
+ */
491
+ declare function rethrowAsGscApiError(prefix: string): (err: unknown) => never;
492
+ /**
493
+ * String-matches an error against the signals Google returns when a token
494
+ * has lost access to a property (revoked, downgraded, or never had it).
495
+ * Cheaper than a full {@link classifyError} call when the caller only
496
+ * needs the permission verdict.
497
+ */
498
+ declare function isPermissionDeniedError(err: unknown): boolean;
499
+ /** CLI-facing formatter. Returns an ANSI-colored multi-line string. */
500
+ declare function formatErrorForCli(cause: unknown): string;
501
+ interface Ok<A> {
502
+ readonly ok: true;
503
+ readonly value: A;
504
+ }
505
+ interface Err<E> {
506
+ readonly ok: false;
507
+ readonly error: E;
508
+ }
509
+ type Result<A, E> = Ok<A> | Err<E>;
510
+ declare function ok<A>(value: A): Ok<A>;
511
+ declare function err<E>(error: E): Err<E>;
512
+ declare function isOk<A, E>(result: Result<A, E>): result is Ok<A>;
513
+ declare function isErr<A, E>(result: Result<A, E>): result is Err<E>;
514
+ /** Maps the success channel, leaving an `Err` untouched. */
515
+ declare function mapResult<A, B, E>(result: Result<A, E>, f: (value: A) => B): Result<B, E>;
516
+ /** Maps the error channel, leaving an `Ok` untouched. */
517
+ declare function mapErr<A, E, F>(result: Result<A, E>, f: (error: E) => F): Result<A, F>;
518
+ /** Folds both channels into a single value. */
519
+ declare function matchResult<A, E, R>(result: Result<A, E>, handlers: {
520
+ onOk: (value: A) => R;
521
+ onErr: (error: E) => R;
522
+ }): R;
523
+ /**
524
+ * Collapses a `Result` back into the throwing world: returns the value or throws
525
+ * via `toError`. Used by the throwing wrappers that sit over the `Result` core so
526
+ * existing call sites keep their `await`/`throw` ergonomics.
527
+ */
528
+ declare function unwrapResult<A, E>(result: Result<A, E>, toError: (error: E) => unknown): A;
426
529
  interface OAuthTokens {
427
530
  accessToken: string;
428
531
  /** Unix seconds. */
429
532
  expiresAt: number;
430
533
  }
534
+ /**
535
+ * Errors-as-values core for {@link refreshAccessToken}: returns a classified
536
+ * `GscError` instead of throwing, so token-storage callers can branch on
537
+ * `error.kind` — `auth-expired` (the refresh token is permanently bad, re-auth)
538
+ * vs `transport` (transient, safe to retry later) — rather than string-matching.
539
+ */
540
+ declare function refreshAccessTokenResult(refreshToken: string, clientId: string, clientSecret: string): Promise<Result<OAuthTokens, GscError>>;
431
541
  /**
432
542
  * Exchange a refresh token for an access token. Retries transient network
433
543
  * failures; surfaces HTTP failures (invalid_grant, etc.) immediately as
434
544
  * `GscApiError` so callers can mark the refresh token as bad.
435
545
  */
436
546
  declare function refreshAccessToken(refreshToken: string, clientId: string, clientSecret: string): Promise<OAuthTokens>;
547
+ /**
548
+ * Errors-as-values core for {@link exchangeAuthCode}: same `auth-expired` vs
549
+ * `transport` classification as {@link refreshAccessTokenResult}.
550
+ */
551
+ declare function exchangeAuthCodeResult(code: string, clientId: string, clientSecret: string, redirectUri: string): Promise<Result<OAuthTokens & {
552
+ refreshToken?: string;
553
+ }, GscError>>;
437
554
  /**
438
555
  * Exchange an authorization code (from the OAuth consent redirect) for
439
556
  * access + refresh tokens. Same retry / surface model as `refreshAccessToken`.
@@ -539,74 +656,6 @@ declare function rowWithMetricDefaults(row: {
539
656
  position: number;
540
657
  };
541
658
  declare function progressBar(current: number, total: number, label: string, width?: number): string;
542
- type GscErrorKind = 'auth-expired' | 'rate-limited' | 'not-found' | 'validation' | 'storage' | 'transport';
543
- type GscError = {
544
- kind: 'auth-expired';
545
- message: string;
546
- cause: unknown;
547
- } | {
548
- kind: 'rate-limited';
549
- message: string;
550
- retryAfter?: number;
551
- cause: unknown;
552
- } | {
553
- kind: 'not-found';
554
- message: string;
555
- cause: unknown;
556
- } | {
557
- kind: 'validation';
558
- message: string;
559
- cause: unknown;
560
- } | {
561
- kind: 'storage';
562
- message: string;
563
- cause: unknown;
564
- } | {
565
- kind: 'transport';
566
- message: string;
567
- status?: number;
568
- cause: unknown;
569
- };
570
- /** Approximate per-day GSC API quotas, used in CLI messaging. */
571
- declare const GSC_QUOTAS: {
572
- readonly searchAnalytics: 25000;
573
- readonly urlInspection: 2000;
574
- readonly indexing: 200;
575
- };
576
- /**
577
- * Classify an unknown error into a `GscError` discriminated union.
578
- * Transport is the catch-all — anything without a recognizable status ends up there.
579
- */
580
- declare function classifyError(cause: unknown): GscError;
581
- /** Construct a storage-kind error from inside the analytics engine / adapters. */
582
- declare function storageError(message: string, cause?: unknown): GscError;
583
- interface GscApiErrorInfo {
584
- code: number;
585
- message: string;
586
- reason?: string;
587
- status?: string;
588
- }
589
- declare function parseGoogleError(text: string, httpStatus?: number): GscApiErrorInfo;
590
- declare class GscApiError extends Error {
591
- info: GscApiErrorInfo;
592
- constructor(message: string, info: GscApiErrorInfo);
593
- }
594
- /**
595
- * Returns a handler that re-throws `unknown` as a `GscApiError` prefixed with
596
- * `prefix`. Recognises `ofetch` `FetchError` (parses `err.data` as Google JSON
597
- * via `parseGoogleError`); passes through existing `GscApiError`; re-throws
598
- * anything else as-is.
599
- */
600
- declare function rethrowAsGscApiError(prefix: string): (err: unknown) => never;
601
- /**
602
- * String-matches an error against the signals Google returns when a token
603
- * has lost access to a property (revoked, downgraded, or never had it).
604
- * Cheaper than a full {@link classifyError} call when the caller only
605
- * needs the permission verdict.
606
- */
607
- declare function isPermissionDeniedError(err: unknown): boolean;
608
- /** CLI-facing formatter. Returns an ANSI-colored multi-line string. */
609
- declare function formatErrorForCli(cause: unknown): string;
610
659
  /** Milliseconds in a UTC day. Use for date arithmetic without DST surprises. */
611
660
  declare const MS_PER_DAY = 86400000;
612
661
  /** Format a Date as YYYY-MM-DD (UTC). */
@@ -919,4 +968,4 @@ declare function normalizeSiteUrl(siteUrl: string): string;
919
968
  * Example: `https://www.Example.com/Foo/` → `example.com/foo`
920
969
  */
921
970
  declare function urlMatchKey(url: string): string | null;
922
- 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, decodeSiteId, deleteSite, deleteSitemap, discoverSitemap, encodeSiteId, 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, normalizeSiteUrl, normalizeUrl, parseGoogleError, parseGrantedScopes, parseGscSiteUrl, pickBestGscProperty, progressBar, propertyNextActions, propertyStatuses, querySourceModes, refreshAccessToken, requestIndexing, rethrowAsGscApiError, rowWithMetricDefaults, runSequentialBatch, siteUrlToVerificationSite, sitemapNextActions, sitemapStatuses, storageError, submitSitemap, toIsoDate, unverifySite, urlMatchKey, verificationMethodsFor, verifySite };
971
+ export { AccountNextAction, AccountStatus, AnalyticsNextAction, AnalyticsStatus, ApiSite, ApiSitemap, ApiSitemapContent, Auth, AuthClient, AuthOptions, BackfillProgress, CallOptions, DAYS_PER_RANGE, DataRow, DimensionFilter, DimensionFilterGroup, DiscoverSitemapOptions, Err, 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, Ok, ParsedGscSiteUrl, ParsedIndexingResult, PartnerLifecycleAccount, PartnerLifecycleResponse, PartnerLifecycleSite, Period, PropertyNextAction, PropertyStatus, PublishUrlNotificationResponse, QueryReturn, QuerySourceMode, RequiredNonNullable, ResolvedAnalyticsRange, Result, 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, decodeSiteId, deleteSite, deleteSitemap, discoverSitemap, encodeSiteId, err, exchangeAuthCode, exchangeAuthCodeResult, 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, gscErrorToException, gscPropertyMatchesTarget, gscdumpApi, hasGscReadScope, hasGscWriteScope, hasIndexingScope, hasOptionalIndexingScope, hasRequiredAnalyticsScope, indexingNextActions, indexingStatuses, inspectUrl, inspectUrlFlat, isErr, isOk, isPermissionDeniedError, isValidGscDate, isVerifiedGscPermission, isVerifiedGscProperty, lifecycleErrorCodes, lifecycleWebhookEvents, listVerifiedSites, mapErr, mapResult, matchGscSite, matchResult, normalizeGscSiteUrl, normalizeRegistrationTarget, normalizeSiteUrl, normalizeUrl, ok, parseGoogleError, parseGrantedScopes, parseGscSiteUrl, pickBestGscProperty, progressBar, propertyNextActions, propertyStatuses, querySourceModes, refreshAccessToken, refreshAccessTokenResult, requestIndexing, rethrowAsGscApiError, rowWithMetricDefaults, runSequentialBatch, siteUrlToVerificationSite, sitemapNextActions, sitemapStatuses, storageError, submitSitemap, toIsoDate, unverifySite, unwrapResult, urlMatchKey, verificationMethodsFor, verifySite };
package/dist/index.mjs CHANGED
@@ -327,6 +327,12 @@ function storageError(message, cause) {
327
327
  cause
328
328
  };
329
329
  }
330
+ function gscErrorToException(error) {
331
+ const exception = new Error(error.message);
332
+ if (error.cause !== void 0) exception.cause = error.cause;
333
+ exception.gscError = error;
334
+ return exception;
335
+ }
330
336
  function parseGoogleError(text, httpStatus) {
331
337
  let parsed = null;
332
338
  try {
@@ -409,6 +415,37 @@ function formatErrorForCli(cause) {
409
415
  }
410
416
  return lines.join("\n");
411
417
  }
418
+ function ok(value) {
419
+ return {
420
+ ok: true,
421
+ value
422
+ };
423
+ }
424
+ function err(error) {
425
+ return {
426
+ ok: false,
427
+ error
428
+ };
429
+ }
430
+ function isOk(result) {
431
+ return result.ok;
432
+ }
433
+ function isErr(result) {
434
+ return !result.ok;
435
+ }
436
+ function mapResult(result, f) {
437
+ return result.ok ? ok(f(result.value)) : result;
438
+ }
439
+ function mapErr(result, f) {
440
+ return result.ok ? result : err(f(result.error));
441
+ }
442
+ function matchResult(result, handlers) {
443
+ return result.ok ? handlers.onOk(result.value) : handlers.onErr(result.error);
444
+ }
445
+ function unwrapResult(result, toError) {
446
+ if (result.ok) return result.value;
447
+ throw toError(result.error);
448
+ }
412
449
  const OAUTH_TOKEN_URL = "https://oauth2.googleapis.com/token";
413
450
  const OAUTH_TIMEOUT_MS = 8e3;
414
451
  const OAUTH_MAX_ATTEMPTS = 3;
@@ -417,29 +454,53 @@ const OAUTH_BACKOFF_MS = [
417
454
  400,
418
455
  1500
419
456
  ];
420
- async function refreshAccessToken(refreshToken, clientId, clientSecret) {
421
- return postOAuthToken(new URLSearchParams({
457
+ async function refreshAccessTokenResult(refreshToken, clientId, clientSecret) {
458
+ return postOAuthTokenResult(new URLSearchParams({
422
459
  client_id: clientId,
423
460
  client_secret: clientSecret,
424
461
  refresh_token: refreshToken,
425
462
  grant_type: "refresh_token"
426
463
  }), "refresh");
427
464
  }
428
- async function exchangeAuthCode(code, clientId, clientSecret, redirectUri) {
429
- const tokens = await postOAuthToken(new URLSearchParams({
465
+ async function refreshAccessToken(refreshToken, clientId, clientSecret) {
466
+ return unwrapResult(await refreshAccessTokenResult(refreshToken, clientId, clientSecret), oauthErrorToException);
467
+ }
468
+ async function exchangeAuthCodeResult(code, clientId, clientSecret, redirectUri) {
469
+ const result = await postOAuthTokenResult(new URLSearchParams({
430
470
  client_id: clientId,
431
471
  client_secret: clientSecret,
432
472
  code,
433
473
  redirect_uri: redirectUri,
434
474
  grant_type: "authorization_code"
435
475
  }), "exchange");
436
- const refreshToken = tokens.refresh_token;
437
- return {
438
- ...tokens,
476
+ if (!result.ok) return result;
477
+ const refreshToken = result.value.refresh_token;
478
+ return ok({
479
+ ...result.value,
439
480
  refreshToken
481
+ });
482
+ }
483
+ async function exchangeAuthCode(code, clientId, clientSecret, redirectUri) {
484
+ return unwrapResult(await exchangeAuthCodeResult(code, clientId, clientSecret, redirectUri), oauthErrorToException);
485
+ }
486
+ function oauthHttpError(op, info) {
487
+ const message = `Failed to ${op} token: ${info.message}`;
488
+ const cause = new GscApiError(message, info);
489
+ return info.reason === "invalid_grant" || info.code === 400 || info.code === 401 || info.code === 403 ? {
490
+ kind: "auth-expired",
491
+ message,
492
+ cause
493
+ } : {
494
+ kind: "transport",
495
+ message,
496
+ status: info.code,
497
+ cause
440
498
  };
441
499
  }
442
- async function postOAuthToken(body, op) {
500
+ function oauthErrorToException(error) {
501
+ return error.cause instanceof Error ? error.cause : gscErrorToException(error);
502
+ }
503
+ async function postOAuthTokenResult(body, op) {
443
504
  let lastError;
444
505
  for (let attempt = 0; attempt < OAUTH_MAX_ATTEMPTS; attempt++) {
445
506
  if (OAUTH_BACKOFF_MS[attempt]) await new Promise((r) => setTimeout(r, OAUTH_BACKOFF_MS[attempt]));
@@ -448,23 +509,24 @@ async function postOAuthToken(body, op) {
448
509
  headers: { "Content-Type": "application/x-www-form-urlencoded" },
449
510
  body,
450
511
  signal: AbortSignal.timeout(OAUTH_TIMEOUT_MS)
451
- }).catch((err) => {
452
- lastError = err;
512
+ }).catch((error) => {
513
+ lastError = error;
453
514
  return null;
454
515
  });
455
516
  if (!res) continue;
456
- if (!res.ok) {
457
- const info = parseGoogleError(await res.text(), res.status);
458
- throw new GscApiError(`Failed to ${op} token: ${info.message}`, info);
459
- }
517
+ if (!res.ok) return err(oauthHttpError(op, parseGoogleError(await res.text(), res.status)));
460
518
  const data = await res.json();
461
- return {
519
+ return ok({
462
520
  accessToken: data.access_token,
463
521
  expiresAt: Math.floor(Date.now() / 1e3) + data.expires_in,
464
522
  refresh_token: data.refresh_token
465
- };
523
+ });
466
524
  }
467
- throw lastError instanceof Error ? lastError : /* @__PURE__ */ new Error(`OAuth ${op} failed after ${OAUTH_MAX_ATTEMPTS} attempts`);
525
+ return err({
526
+ kind: "transport",
527
+ message: lastError instanceof Error ? lastError.message : `OAuth ${op} failed after ${OAUTH_MAX_ATTEMPTS} attempts`,
528
+ cause: lastError
529
+ });
468
530
  }
469
531
  async function fetchSites(client) {
470
532
  return client.sites();
@@ -2152,6 +2214,126 @@ const SearchTypes = {
2152
2214
  GOOGLE_NEWS: "googleNews"
2153
2215
  };
2154
2216
  Object.fromEntries(countries_default.map((c) => [c["alpha-3"], c["alpha-3"].toLowerCase()]));
2217
+ const TIME_AXIS_DIMENSIONS = new Set(["date", "hour"]);
2218
+ const queryErrors = {
2219
+ missingDateRange() {
2220
+ return {
2221
+ kind: "missing-date-range",
2222
+ message: "Date range required: use .where(between(date, start, end)) or .where(and(gte(date, start), lte(date, end)))"
2223
+ };
2224
+ },
2225
+ invalidRowLimit(value) {
2226
+ return {
2227
+ kind: "invalid-row-limit",
2228
+ value,
2229
+ message: `rowLimit must be a positive integer, got ${value}`
2230
+ };
2231
+ },
2232
+ invalidStartRow(value) {
2233
+ return {
2234
+ kind: "invalid-start-row",
2235
+ value,
2236
+ message: `startRow must be a non-negative integer, got ${value}`
2237
+ };
2238
+ },
2239
+ hourDimensionRequiresHourlyState() {
2240
+ return {
2241
+ kind: "invalid-data-state",
2242
+ message: "hour dimension requires dataState: \"hourly_all\""
2243
+ };
2244
+ },
2245
+ hourlyStateRequiresHourDimension() {
2246
+ return {
2247
+ kind: "invalid-data-state",
2248
+ message: "dataState: \"hourly_all\" requires grouping by hour dimension"
2249
+ };
2250
+ },
2251
+ byPropertyUnsupportedSearchType() {
2252
+ return {
2253
+ kind: "invalid-aggregation-type",
2254
+ message: "aggregationType: \"byProperty\" is not supported for type \"discover\" or \"googleNews\""
2255
+ };
2256
+ },
2257
+ byPropertyNotAllowedWithPage() {
2258
+ return {
2259
+ kind: "invalid-aggregation-type",
2260
+ message: "aggregationType: \"byProperty\" is not allowed when grouping or filtering by page"
2261
+ };
2262
+ },
2263
+ byNewsShowcaseRequiresSearchType() {
2264
+ return {
2265
+ kind: "invalid-aggregation-type",
2266
+ message: "aggregationType: \"byNewsShowcasePanel\" requires type \"discover\" or \"googleNews\""
2267
+ };
2268
+ },
2269
+ byNewsShowcaseNotAllowedWithPage() {
2270
+ return {
2271
+ kind: "invalid-aggregation-type",
2272
+ message: "aggregationType: \"byNewsShowcasePanel\" is not allowed when grouping or filtering by page"
2273
+ };
2274
+ },
2275
+ byNewsShowcaseRequiresShowcaseFilter() {
2276
+ return {
2277
+ kind: "invalid-aggregation-type",
2278
+ message: "aggregationType: \"byNewsShowcasePanel\" requires a searchAppearance equals \"NEWS_SHOWCASE\" filter and no other searchAppearance filter"
2279
+ };
2280
+ },
2281
+ invalidBuilderState(cause) {
2282
+ return {
2283
+ kind: "invalid-builder-state",
2284
+ cause,
2285
+ message: "Invalid state"
2286
+ };
2287
+ },
2288
+ unsupportedCapability(capability, context) {
2289
+ return {
2290
+ kind: "unsupported-capability",
2291
+ capability,
2292
+ context,
2293
+ message: `${context} requires ${capability} capability`
2294
+ };
2295
+ },
2296
+ unresolvableDataset(dimensions, filterDims = []) {
2297
+ const grouped = dimensions.filter((d) => !TIME_AXIS_DIMENSIONS.has(d));
2298
+ const filtered = filterDims.filter((d) => !TIME_AXIS_DIMENSIONS.has(d));
2299
+ return {
2300
+ kind: "unresolvable-dataset",
2301
+ dimensions,
2302
+ filterDims,
2303
+ message: `Cannot resolve a [${grouped.join(", ")}] breakdown filtered by [${filtered.join(", ")}] from stored data: these dimensions live in separate per-dimension tables. Only the live GSC API computes cross-dimension aggregates.`
2304
+ };
2305
+ }
2306
+ };
2307
+ var UnsupportedLogicalCapabilityError = class extends Error {
2308
+ queryError;
2309
+ constructor(capability, context) {
2310
+ const error = queryErrors.unsupportedCapability(capability, context);
2311
+ super(error.message);
2312
+ this.name = "UnsupportedLogicalCapabilityError";
2313
+ this.queryError = error;
2314
+ }
2315
+ };
2316
+ var UnresolvableDatasetError = class extends Error {
2317
+ queryError;
2318
+ constructor(dimensions, filterDims = []) {
2319
+ const error = queryErrors.unresolvableDataset(dimensions, filterDims);
2320
+ super(error.message);
2321
+ this.name = "UnresolvableDatasetError";
2322
+ this.queryError = error;
2323
+ }
2324
+ };
2325
+ function queryErrorToException(error) {
2326
+ switch (error.kind) {
2327
+ case "unsupported-capability": return new UnsupportedLogicalCapabilityError(error.capability, error.context);
2328
+ case "unresolvable-dataset": return new UnresolvableDatasetError(error.dimensions, error.filterDims);
2329
+ default: {
2330
+ const exception = new Error(error.message);
2331
+ if ("cause" in error && error.cause !== void 0) exception.cause = error.cause;
2332
+ exception.queryError = error;
2333
+ return exception;
2334
+ }
2335
+ }
2336
+ }
2155
2337
  const DATE_OPERATORS = [
2156
2338
  "gte",
2157
2339
  "gt",
@@ -2229,9 +2411,9 @@ function extractSpecialFilters(filter) {
2229
2411
  dimensionFilter
2230
2412
  };
2231
2413
  }
2232
- function resolveToBody(state) {
2414
+ function resolveToBodyResult(state) {
2233
2415
  const { startDate, endDate, searchType, dimensionFilter } = extractSpecialFilters(state.filter);
2234
- if (!startDate || !endDate) throw new Error("Date range required: use .where(between(date, start, end)) or .where(and(gte(date, start), lte(date, end)))");
2416
+ if (!startDate || !endDate) return err(queryErrors.missingDateRange());
2235
2417
  const body = {
2236
2418
  dimensions: state.dimensions,
2237
2419
  startDate,
@@ -2240,16 +2422,16 @@ function resolveToBody(state) {
2240
2422
  const resolvedType = state.searchType ?? searchType;
2241
2423
  if (resolvedType) body.type = resolvedType;
2242
2424
  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}`);
2425
+ if (!Number.isInteger(state.rowLimit) || state.rowLimit < 1) return err(queryErrors.invalidRowLimit(state.rowLimit));
2244
2426
  body.rowLimit = state.rowLimit;
2245
2427
  }
2246
2428
  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}`);
2429
+ if (!Number.isInteger(state.startRow) || state.startRow < 0) return err(queryErrors.invalidStartRow(state.startRow));
2248
2430
  if (state.startRow > 0) body.startRow = state.startRow;
2249
2431
  }
2250
2432
  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");
2433
+ if (hasHour && state.dataState !== "hourly_all") return err(queryErrors.hourDimensionRequiresHourlyState());
2434
+ if (state.dataState === "hourly_all" && !hasHour) return err(queryErrors.hourlyStateRequiresHourDimension());
2253
2435
  if (state.dataState) body.dataState = state.dataState;
2254
2436
  const filterGroups = resolveFilter(dimensionFilter);
2255
2437
  if (filterGroups.length > 0) body.dimensionFilterGroups = filterGroups;
@@ -2258,20 +2440,23 @@ function resolveToBody(state) {
2258
2440
  const apiLeafFilters = filterGroups.flatMap((g) => g.filters ?? []);
2259
2441
  const filtersByPage = apiLeafFilters.some((f) => f.dimension === "page");
2260
2442
  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");
2443
+ if (body.type === "discover" || body.type === "googleNews") return err(queryErrors.byPropertyUnsupportedSearchType());
2444
+ if (groupsByPage || filtersByPage) return err(queryErrors.byPropertyNotAllowedWithPage());
2263
2445
  }
2264
2446
  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");
2447
+ if (body.type !== "discover" && body.type !== "googleNews") return err(queryErrors.byNewsShowcaseRequiresSearchType());
2448
+ if (groupsByPage || filtersByPage) return err(queryErrors.byNewsShowcaseNotAllowedWithPage());
2267
2449
  const saFilters = apiLeafFilters.filter((f) => f.dimension === "searchAppearance");
2268
2450
  const hasNewsShowcase = saFilters.some((f) => f.operator === "equals" && f.expression === "NEWS_SHOWCASE");
2269
2451
  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");
2452
+ if (!hasNewsShowcase || hasOther) return err(queryErrors.byNewsShowcaseRequiresShowcaseFilter());
2271
2453
  }
2272
2454
  body.aggregationType = state.aggregationType;
2273
2455
  }
2274
- return body;
2456
+ return ok(body);
2457
+ }
2458
+ function resolveToBody(state) {
2459
+ return unwrapResult(resolveToBodyResult(state), queryErrorToException);
2275
2460
  }
2276
2461
  function isApiFilter(f) {
2277
2462
  return !isMetricOperator(f.operator) && !isSpecialOperator(f.operator);
@@ -3072,4 +3257,4 @@ function urlMatchKey(url) {
3072
3257
  if (!u) return null;
3073
3258
  return [u.hostname.replace(/^www\./, ""), u.pathname.replace(/\/$/, "") || "/"].join("").toLowerCase();
3074
3259
  }
3075
- 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, decodeSiteId, deleteSite, deleteSitemap, discoverSitemap, encodeSiteId, 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, normalizeSiteUrl, normalizeUrl, parseGoogleError, parseGrantedScopes, parseGscSiteUrl, pickBestGscProperty, progressBar, propertyNextActions, propertyStatuses, querySourceModes, refreshAccessToken, requestIndexing, rethrowAsGscApiError, rowWithMetricDefaults, runSequentialBatch, siteUrlToVerificationSite, sitemapNextActions, sitemapStatuses, storageError, submitSitemap, toIsoDate, unverifySite, urlMatchKey, verificationMethodsFor, verifySite };
3260
+ 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, decodeSiteId, deleteSite, deleteSitemap, discoverSitemap, encodeSiteId, err, exchangeAuthCode, exchangeAuthCodeResult, 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, gscErrorToException, gscPropertyMatchesTarget, gscdumpApi, hasGscReadScope, hasGscWriteScope, hasIndexingScope, hasOptionalIndexingScope, hasRequiredAnalyticsScope, indexingNextActions, indexingStatuses, inspectUrl, inspectUrlFlat, isErr, isOk, isPermissionDeniedError, isValidGscDate, isVerifiedGscPermission, isVerifiedGscProperty, lifecycleErrorCodes, lifecycleWebhookEvents, listVerifiedSites, mapErr, mapResult, matchGscSite, matchResult, normalizeGscSiteUrl, normalizeRegistrationTarget, normalizeSiteUrl, normalizeUrl, ok, parseGoogleError, parseGrantedScopes, parseGscSiteUrl, pickBestGscProperty, progressBar, propertyNextActions, propertyStatuses, querySourceModes, refreshAccessToken, refreshAccessTokenResult, requestIndexing, rethrowAsGscApiError, rowWithMetricDefaults, runSequentialBatch, siteUrlToVerificationSite, sitemapNextActions, sitemapStatuses, storageError, submitSitemap, toIsoDate, unverifySite, unwrapResult, urlMatchKey, verificationMethodsFor, verifySite };