@agentsean/google 1.0.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.
Files changed (112) hide show
  1. package/LICENSE +661 -0
  2. package/dist/audit-extras.d.ts +21 -0
  3. package/dist/audit-extras.d.ts.map +1 -0
  4. package/dist/audit-extras.js +62 -0
  5. package/dist/audit-extras.js.map +1 -0
  6. package/dist/broker.d.ts +47 -0
  7. package/dist/broker.d.ts.map +1 -0
  8. package/dist/broker.js +146 -0
  9. package/dist/broker.js.map +1 -0
  10. package/dist/causes.d.ts +157 -0
  11. package/dist/causes.d.ts.map +1 -0
  12. package/dist/causes.js +175 -0
  13. package/dist/causes.js.map +1 -0
  14. package/dist/connect-flow.d.ts +63 -0
  15. package/dist/connect-flow.d.ts.map +1 -0
  16. package/dist/connect-flow.js +150 -0
  17. package/dist/connect-flow.js.map +1 -0
  18. package/dist/crux.d.ts +37 -0
  19. package/dist/crux.d.ts.map +1 -0
  20. package/dist/crux.js +112 -0
  21. package/dist/crux.js.map +1 -0
  22. package/dist/data/changepoints.json +174 -0
  23. package/dist/data/quotas.json +17 -0
  24. package/dist/errors.d.ts +46 -0
  25. package/dist/errors.d.ts.map +1 -0
  26. package/dist/errors.js +110 -0
  27. package/dist/errors.js.map +1 -0
  28. package/dist/ga4.d.ts +46 -0
  29. package/dist/ga4.d.ts.map +1 -0
  30. package/dist/ga4.js +126 -0
  31. package/dist/ga4.js.map +1 -0
  32. package/dist/gsc.d.ts +98 -0
  33. package/dist/gsc.d.ts.map +1 -0
  34. package/dist/gsc.js +141 -0
  35. package/dist/gsc.js.map +1 -0
  36. package/dist/http.d.ts +14 -0
  37. package/dist/http.d.ts.map +1 -0
  38. package/dist/http.js +47 -0
  39. package/dist/http.js.map +1 -0
  40. package/dist/incidents.d.ts +51 -0
  41. package/dist/incidents.d.ts.map +1 -0
  42. package/dist/incidents.js +221 -0
  43. package/dist/incidents.js.map +1 -0
  44. package/dist/index.d.ts +27 -0
  45. package/dist/index.d.ts.map +1 -0
  46. package/dist/index.js +27 -0
  47. package/dist/index.js.map +1 -0
  48. package/dist/integrity.d.ts +32 -0
  49. package/dist/integrity.d.ts.map +1 -0
  50. package/dist/integrity.js +52 -0
  51. package/dist/integrity.js.map +1 -0
  52. package/dist/oauth-broker.d.ts +38 -0
  53. package/dist/oauth-broker.d.ts.map +1 -0
  54. package/dist/oauth-broker.js +81 -0
  55. package/dist/oauth-broker.js.map +1 -0
  56. package/dist/oauth-config.d.ts +16 -0
  57. package/dist/oauth-config.d.ts.map +1 -0
  58. package/dist/oauth-config.js +24 -0
  59. package/dist/oauth-config.js.map +1 -0
  60. package/dist/oauth-desktop.d.ts +29 -0
  61. package/dist/oauth-desktop.d.ts.map +1 -0
  62. package/dist/oauth-desktop.js +76 -0
  63. package/dist/oauth-desktop.js.map +1 -0
  64. package/dist/pending.d.ts +18 -0
  65. package/dist/pending.d.ts.map +1 -0
  66. package/dist/pending.js +29 -0
  67. package/dist/pending.js.map +1 -0
  68. package/dist/persist.d.ts +48 -0
  69. package/dist/persist.d.ts.map +1 -0
  70. package/dist/persist.js +298 -0
  71. package/dist/persist.js.map +1 -0
  72. package/dist/pkce.d.ts +9 -0
  73. package/dist/pkce.d.ts.map +1 -0
  74. package/dist/pkce.js +13 -0
  75. package/dist/pkce.js.map +1 -0
  76. package/dist/psi.d.ts +19 -0
  77. package/dist/psi.d.ts.map +1 -0
  78. package/dist/psi.js +47 -0
  79. package/dist/psi.js.map +1 -0
  80. package/dist/quota.d.ts +22 -0
  81. package/dist/quota.d.ts.map +1 -0
  82. package/dist/quota.js +136 -0
  83. package/dist/quota.js.map +1 -0
  84. package/dist/reconcile.d.ts +19 -0
  85. package/dist/reconcile.d.ts.map +1 -0
  86. package/dist/reconcile.js +89 -0
  87. package/dist/reconcile.js.map +1 -0
  88. package/dist/scopes.d.ts +25 -0
  89. package/dist/scopes.d.ts.map +1 -0
  90. package/dist/scopes.js +49 -0
  91. package/dist/scopes.js.map +1 -0
  92. package/dist/seal.d.ts +4 -0
  93. package/dist/seal.d.ts.map +1 -0
  94. package/dist/seal.js +27 -0
  95. package/dist/seal.js.map +1 -0
  96. package/dist/sync.d.ts +29 -0
  97. package/dist/sync.d.ts.map +1 -0
  98. package/dist/sync.js +266 -0
  99. package/dist/sync.js.map +1 -0
  100. package/dist/tokens.d.ts +57 -0
  101. package/dist/tokens.d.ts.map +1 -0
  102. package/dist/tokens.js +122 -0
  103. package/dist/tokens.js.map +1 -0
  104. package/dist/verification.d.ts +17 -0
  105. package/dist/verification.d.ts.map +1 -0
  106. package/dist/verification.js +51 -0
  107. package/dist/verification.js.map +1 -0
  108. package/dist/waterfall.d.ts +39 -0
  109. package/dist/waterfall.d.ts.map +1 -0
  110. package/dist/waterfall.js +167 -0
  111. package/dist/waterfall.js.map +1 -0
  112. package/package.json +52 -0
@@ -0,0 +1,63 @@
1
+ import type { CredentialStore } from "@agentsean/credentials";
2
+ import type { SqliteDatabase } from "@agentsean/db";
3
+ import { loopbackRedirectUri } from "./oauth-config.js";
4
+ import type { PendingStore } from "./pending.js";
5
+ import { saveApiKey, type StoredGoogleGrant } from "./tokens.js";
6
+ export type ConnectStartInput = {
7
+ mode?: "broker" | "byo" | undefined;
8
+ redirectUri: string;
9
+ siteId?: string | null | undefined;
10
+ credentialsJson?: string | undefined;
11
+ clientId?: string | undefined;
12
+ clientSecret?: string | undefined;
13
+ };
14
+ export declare function startConnect(opts: {
15
+ store: CredentialStore;
16
+ pending: PendingStore;
17
+ input: ConnectStartInput;
18
+ env?: NodeJS.ProcessEnv | undefined;
19
+ }): Promise<{
20
+ authorizationUrl: string;
21
+ state: string;
22
+ mode: "broker" | "byo";
23
+ }>;
24
+ export declare function finishConnect(opts: {
25
+ store: CredentialStore;
26
+ pending: PendingStore;
27
+ query: {
28
+ code?: string | undefined;
29
+ state?: string | undefined;
30
+ payload?: string | undefined;
31
+ };
32
+ fetch?: typeof fetch | undefined;
33
+ env?: NodeJS.ProcessEnv | undefined;
34
+ }): Promise<StoredGoogleGrant>;
35
+ export declare function discoverProperties(opts: {
36
+ db: SqliteDatabase;
37
+ store: CredentialStore;
38
+ fetch?: typeof fetch | undefined;
39
+ siteId?: string | null | undefined;
40
+ }): Promise<{
41
+ email: string | null;
42
+ testingModeSuspected: boolean;
43
+ gscSites: {
44
+ siteUrl: string;
45
+ permissionLevel: string;
46
+ }[];
47
+ ga4Properties: {
48
+ propertyId: string;
49
+ displayName: string;
50
+ accountId: string;
51
+ accountDisplayName: string;
52
+ }[];
53
+ suggestedGsc: string | null;
54
+ }>;
55
+ export declare function bindProperties(opts: {
56
+ db: SqliteDatabase;
57
+ store: CredentialStore;
58
+ siteId: string;
59
+ gscSiteUrl?: string | null | undefined;
60
+ ga4PropertyId?: string | null | undefined;
61
+ }): Promise<void>;
62
+ export { loopbackRedirectUri, saveApiKey };
63
+ //# sourceMappingURL=connect-flow.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"connect-flow.d.ts","sourceRoot":"","sources":["../src/connect-flow.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,150 @@
1
+ import { sites } from "@agentsean/db";
2
+ import { eq } from "drizzle-orm";
3
+ import { resolveOAuthConfig, loopbackRedirectUri, parseDesktopClientJson, } from "./oauth-config.js";
4
+ import { startByoAuthorization, exchangeByoCode } from "./oauth-desktop.js";
5
+ import { grantFromBrokerHandoff, startBrokerAuthorization } from "./oauth-broker.js";
6
+ import { loadByoClient, loadGrant, saveByoClient, saveGrant, saveApiKey, } from "./tokens.js";
7
+ import { createGscClient } from "./gsc.js";
8
+ import { createGa4Client } from "./ga4.js";
9
+ import { createQuotaManager } from "./quota.js";
10
+ import { defaultSleep } from "./http.js";
11
+ import { upsertGa4Connection, upsertGscConnection } from "./persist.js";
12
+ import { GOOGLE_USERINFO_URL } from "./scopes.js";
13
+ import { GscTokenError } from "./errors.js";
14
+ import { normalizeGscSiteUrl } from "./scopes.js";
15
+ export async function startConnect(opts) {
16
+ const cfg = resolveOAuthConfig(opts.env);
17
+ let byo = cfg.byo;
18
+ if (opts.input.credentialsJson) {
19
+ byo = parseDesktopClientJson(opts.input.credentialsJson);
20
+ }
21
+ else if (opts.input.clientId && opts.input.clientSecret) {
22
+ byo = { clientId: opts.input.clientId, clientSecret: opts.input.clientSecret };
23
+ }
24
+ const mode = opts.input.mode ?? (byo ? "byo" : "broker");
25
+ if (mode === "byo") {
26
+ if (!byo) {
27
+ throw new GscTokenError("BYO Google Cloud project requires GOOGLE_CLIENT_ID and GOOGLE_CLIENT_SECRET, or --credentials client_secret.json.");
28
+ }
29
+ await saveByoClient(opts.store, byo);
30
+ const started = startByoAuthorization({
31
+ client: byo,
32
+ redirectUri: opts.input.redirectUri,
33
+ pending: opts.pending,
34
+ siteId: opts.input.siteId,
35
+ });
36
+ return { ...started, mode: "byo" };
37
+ }
38
+ const started = startBrokerAuthorization({
39
+ brokerUrl: cfg.brokerUrl,
40
+ redirectUri: opts.input.redirectUri,
41
+ pending: opts.pending,
42
+ siteId: opts.input.siteId,
43
+ });
44
+ return {
45
+ authorizationUrl: started.authorizationUrl,
46
+ state: started.state,
47
+ mode: "broker",
48
+ };
49
+ }
50
+ export async function finishConnect(opts) {
51
+ const state = opts.query.state;
52
+ if (!state)
53
+ throw new GscTokenError("OAuth callback missing state.");
54
+ const pending = opts.pending.take(state);
55
+ if (!pending)
56
+ throw new GscTokenError("OAuth state is unknown or expired. Start connect again.");
57
+ const prev = await loadGrant(opts.store);
58
+ const fetchFn = opts.fetch ?? fetch;
59
+ if (pending.mode === "broker") {
60
+ if (!opts.query.payload)
61
+ throw new GscTokenError("Broker callback missing sealed payload.");
62
+ const grant = grantFromBrokerHandoff(pending, opts.query.payload, prev);
63
+ await saveGrant(opts.store, grant);
64
+ return grant;
65
+ }
66
+ if (!opts.query.code)
67
+ throw new GscTokenError("OAuth callback missing code.");
68
+ const cfg = resolveOAuthConfig(opts.env);
69
+ const byo = (await loadByoClient(opts.store)) ?? cfg.byo;
70
+ if (!byo)
71
+ throw new GscTokenError("BYO client credentials missing at token exchange.");
72
+ const grant = await exchangeByoCode({
73
+ client: byo,
74
+ pending,
75
+ code: opts.query.code,
76
+ fetch: fetchFn,
77
+ prev,
78
+ });
79
+ await saveGrant(opts.store, grant);
80
+ return grant;
81
+ }
82
+ export async function discoverProperties(opts) {
83
+ const grant = await loadGrant(opts.store);
84
+ if (!grant)
85
+ throw new GscTokenError("Google is not connected.");
86
+ const http = {
87
+ fetch: opts.fetch ?? fetch,
88
+ quota: createQuotaManager(opts.db),
89
+ maxRetries: 2,
90
+ sleep: defaultSleep,
91
+ maxBackoffMs: 1000,
92
+ };
93
+ const getToken = async () => grant.accessToken;
94
+ const gsc = createGscClient({ http, getToken });
95
+ const ga4 = createGa4Client({ http, getToken });
96
+ let email = grant.email;
97
+ try {
98
+ const res = await http.fetch(GOOGLE_USERINFO_URL, {
99
+ headers: { Authorization: `Bearer ${grant.accessToken}` },
100
+ });
101
+ if (res.ok) {
102
+ const info = (await res.json());
103
+ email = typeof info.email === "string" ? info.email : email;
104
+ }
105
+ }
106
+ catch {
107
+ // ignore
108
+ }
109
+ const gscSites = await gsc.listSites();
110
+ const ga4Properties = await ga4.listProperties();
111
+ let suggestedGsc = null;
112
+ if (opts.siteId) {
113
+ const site = opts.db.select().from(sites).where(eq(sites.id, opts.siteId)).get();
114
+ if (site) {
115
+ const want = normalizeGscSiteUrl(site.origin);
116
+ suggestedGsc =
117
+ gscSites.find((s) => s.siteUrl === want)?.siteUrl ??
118
+ gscSites.find((s) => site.origin.startsWith(s.siteUrl.replace(/\/$/, "")))
119
+ ?.siteUrl ??
120
+ null;
121
+ }
122
+ }
123
+ return {
124
+ email,
125
+ testingModeSuspected: grant.testingModeSuspected,
126
+ gscSites,
127
+ ga4Properties,
128
+ suggestedGsc,
129
+ };
130
+ }
131
+ export async function bindProperties(opts) {
132
+ const grant = await loadGrant(opts.store);
133
+ if (opts.gscSiteUrl) {
134
+ upsertGscConnection(opts.db, {
135
+ siteId: opts.siteId,
136
+ siteUrl: opts.gscSiteUrl,
137
+ accountEmail: grant?.email,
138
+ googleSub: grant?.googleSub,
139
+ });
140
+ }
141
+ if (opts.ga4PropertyId) {
142
+ upsertGa4Connection(opts.db, {
143
+ siteId: opts.siteId,
144
+ propertyId: opts.ga4PropertyId,
145
+ accountEmail: grant?.email,
146
+ });
147
+ }
148
+ }
149
+ export { loopbackRedirectUri, saveApiKey };
150
+ //# sourceMappingURL=connect-flow.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"connect-flow.js","sourceRoot":"","sources":["../src/connect-flow.ts"],"names":[],"mappings":"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"}
package/dist/crux.d.ts ADDED
@@ -0,0 +1,37 @@
1
+ import { type GoogleHttp } from "./http.js";
2
+ export type CruxFormFactor = "PHONE" | "DESKTOP" | "TABLET" | "ALL";
3
+ export type CruxMetrics = {
4
+ lcpP75: number | null;
5
+ inpP75: number | null;
6
+ clsP75: number | null;
7
+ ttfbP75: number | null;
8
+ fcpP75: number | null;
9
+ };
10
+ export type CruxRecord = CruxMetrics & {
11
+ identifier: string;
12
+ identifierKind: "url" | "origin";
13
+ formFactor: CruxFormFactor;
14
+ collectionDate: string;
15
+ insufficientTraffic: boolean;
16
+ sourceApi: "crux" | "crux_history";
17
+ raw: unknown;
18
+ };
19
+ /**
20
+ * URL-level 404 is normal (insufficient traffic), not an error.
21
+ * Fall back to origin-level.
22
+ */
23
+ export declare function queryCruxWithFallback(opts: {
24
+ http: GoogleHttp;
25
+ apiKey: string;
26
+ url: string;
27
+ origin: string;
28
+ formFactor?: CruxFormFactor | undefined;
29
+ }): Promise<CruxRecord>;
30
+ export declare function queryCruxHistory(opts: {
31
+ http: GoogleHttp;
32
+ apiKey: string;
33
+ origin: string;
34
+ formFactor?: CruxFormFactor | undefined;
35
+ collectionPeriodCount?: number | undefined;
36
+ }): Promise<unknown>;
37
+ //# sourceMappingURL=crux.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"crux.d.ts","sourceRoot":"","sources":["../src/crux.ts"],"names":[],"mappings":"AAAA,OAAO,EAAe,KAAK,UAAU,EAAE,MAAM,WAAW,CAAC;AAMzD,MAAM,MAAM,cAAc,GAAG,OAAO,GAAG,SAAS,GAAG,QAAQ,GAAG,KAAK,CAAC;AAEpE,MAAM,MAAM,WAAW,GAAG;IACxB,MAAM,EAAE,MAAM,GAAG,IAAI,CAAC;IACtB,MAAM,EAAE,MAAM,GAAG,IAAI,CAAC;IACtB,MAAM,EAAE,MAAM,GAAG,IAAI,CAAC;IACtB,OAAO,EAAE,MAAM,GAAG,IAAI,CAAC;IACvB,MAAM,EAAE,MAAM,GAAG,IAAI,CAAC;CACvB,CAAC;AAEF,MAAM,MAAM,UAAU,GAAG,WAAW,GAAG;IACrC,UAAU,EAAE,MAAM,CAAC;IACnB,cAAc,EAAE,KAAK,GAAG,QAAQ,CAAC;IACjC,UAAU,EAAE,cAAc,CAAC;IAC3B,cAAc,EAAE,MAAM,CAAC;IACvB,mBAAmB,EAAE,OAAO,CAAC;IAC7B,SAAS,EAAE,MAAM,GAAG,cAAc,CAAC;IACnC,GAAG,EAAE,OAAO,CAAC;CACd,CAAC;AAyCF;;;GAGG;AACH,wBAAsB,qBAAqB,CAAC,IAAI,EAAE;IAChD,IAAI,EAAE,UAAU,CAAC;IACjB,MAAM,EAAE,MAAM,CAAC;IACf,GAAG,EAAE,MAAM,CAAC;IACZ,MAAM,EAAE,MAAM,CAAC;IACf,UAAU,CAAC,EAAE,cAAc,GAAG,SAAS,CAAC;CACzC,GAAG,OAAO,CAAC,UAAU,CAAC,CA+DtB;AAED,wBAAsB,gBAAgB,CAAC,IAAI,EAAE;IAC3C,IAAI,EAAE,UAAU,CAAC;IACjB,MAAM,EAAE,MAAM,CAAC;IACf,MAAM,EAAE,MAAM,CAAC;IACf,UAAU,CAAC,EAAE,cAAc,GAAG,SAAS,CAAC;IACxC,qBAAqB,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;CAC5C,GAAG,OAAO,CAAC,OAAO,CAAC,CAanB"}
package/dist/crux.js ADDED
@@ -0,0 +1,112 @@
1
+ import { googleFetch } from "./http.js";
2
+ const CRUX = "https://chromeuxreport.googleapis.com/v1/records:queryRecord";
3
+ const CRUX_HISTORY = "https://chromeuxreport.googleapis.com/v1/records:queryHistoryRecord";
4
+ function p75(metrics, name) {
5
+ if (!metrics || typeof metrics !== "object")
6
+ return null;
7
+ const m = metrics[name];
8
+ const v = m?.percentiles?.p75;
9
+ return typeof v === "number" ? v : null;
10
+ }
11
+ function metricsOf(obj) {
12
+ return {
13
+ lcpP75: p75(obj, "largest_contentful_paint"),
14
+ inpP75: p75(obj, "interaction_to_next_paint"),
15
+ clsP75: p75(obj, "cumulative_layout_shift"),
16
+ ttfbP75: p75(obj, "experimental_time_to_first_byte"),
17
+ fcpP75: p75(obj, "first_contentful_paint"),
18
+ };
19
+ }
20
+ async function queryCrux(opts) {
21
+ const endpoint = opts.history ? CRUX_HISTORY : CRUX;
22
+ const res = await googleFetch(opts.http, "crux", "project", `${endpoint}?key=${encodeURIComponent(opts.apiKey)}`, {
23
+ method: "POST",
24
+ headers: { "Content-Type": "application/json" },
25
+ body: JSON.stringify(opts.body),
26
+ });
27
+ const json = await res.json().catch(() => ({}));
28
+ return { status: res.status, json };
29
+ }
30
+ /**
31
+ * URL-level 404 is normal (insufficient traffic), not an error.
32
+ * Fall back to origin-level.
33
+ */
34
+ export async function queryCruxWithFallback(opts) {
35
+ const formFactor = opts.formFactor ?? "PHONE";
36
+ const today = new Date().toISOString().slice(0, 10);
37
+ const ff = formFactor === "ALL" ? undefined : formFactor;
38
+ const urlBody = { url: opts.url };
39
+ if (ff)
40
+ urlBody.formFactor = ff;
41
+ const urlHit = await queryCrux({
42
+ http: opts.http,
43
+ apiKey: opts.apiKey,
44
+ body: urlBody,
45
+ history: false,
46
+ });
47
+ if (urlHit.status === 200) {
48
+ const rec = urlHit.json;
49
+ return {
50
+ identifier: opts.url,
51
+ identifierKind: "url",
52
+ formFactor,
53
+ collectionDate: today,
54
+ insufficientTraffic: false,
55
+ sourceApi: "crux",
56
+ raw: urlHit.json,
57
+ ...metricsOf(rec.record?.metrics),
58
+ };
59
+ }
60
+ const originBody = { origin: opts.origin };
61
+ if (ff)
62
+ originBody.formFactor = ff;
63
+ const originHit = await queryCrux({
64
+ http: opts.http,
65
+ apiKey: opts.apiKey,
66
+ body: originBody,
67
+ history: false,
68
+ });
69
+ if (originHit.status === 200) {
70
+ const rec = originHit.json;
71
+ return {
72
+ identifier: opts.origin,
73
+ identifierKind: "origin",
74
+ formFactor,
75
+ collectionDate: today,
76
+ insufficientTraffic: urlHit.status === 404,
77
+ sourceApi: "crux",
78
+ raw: originHit.json,
79
+ ...metricsOf(rec.record?.metrics),
80
+ };
81
+ }
82
+ return {
83
+ identifier: opts.origin,
84
+ identifierKind: "origin",
85
+ formFactor,
86
+ collectionDate: today,
87
+ insufficientTraffic: true,
88
+ sourceApi: "crux",
89
+ raw: originHit.json,
90
+ lcpP75: null,
91
+ inpP75: null,
92
+ clsP75: null,
93
+ ttfbP75: null,
94
+ fcpP75: null,
95
+ };
96
+ }
97
+ export async function queryCruxHistory(opts) {
98
+ const body = {
99
+ origin: opts.origin,
100
+ collectionPeriodCount: opts.collectionPeriodCount ?? 25,
101
+ };
102
+ if (opts.formFactor && opts.formFactor !== "ALL")
103
+ body.formFactor = opts.formFactor;
104
+ const hit = await queryCrux({
105
+ http: opts.http,
106
+ apiKey: opts.apiKey,
107
+ body,
108
+ history: true,
109
+ });
110
+ return hit.json;
111
+ }
112
+ //# sourceMappingURL=crux.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"crux.js","sourceRoot":"","sources":["../src/crux.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,174 @@
1
+ [
2
+ {
3
+ "id": "gsc-impressions-logging-error-2025",
4
+ "kind": "DATA_ANOMALY",
5
+ "begin": "2025-05-13",
6
+ "end": "2026-04-27",
7
+ "title": "GSC impressions logging error",
8
+ "metricImpact": ["impressions", "ctr", "position"],
9
+ "clicksAffected": false,
10
+ "impressionsAffected": true,
11
+ "positionAffected": true,
12
+ "source": "curated",
13
+ "notes": "Google Data Anomalies: impressions, CTR and average position inaccurate 2025-05-13 → 2026-04-27. Clicks unaffected. Never backfilled. Default every metric to clicks."
14
+ },
15
+ {
16
+ "id": "gsc-num100-removal-2025",
17
+ "kind": "DATA_COLLECTION_CHANGE",
18
+ "begin": "2025-09-10",
19
+ "end": "2025-09-14",
20
+ "title": "&num=100 parameter removed",
21
+ "metricImpact": ["impressions", "position"],
22
+ "clicksAffected": false,
23
+ "impressionsAffected": true,
24
+ "positionAffected": true,
25
+ "source": "curated",
26
+ "notes": "Google stopped honoring &num=100 ~2025-09-10–14. Bot-inflated impressions vanished; clicks stayed flat; average position improved mechanically. Never officially announced. Naive YoY impression comparison is a false decay alert."
27
+ },
28
+ {
29
+ "id": "gsc-ai-mode-foldin-2025-06-17",
30
+ "kind": "DATA_COLLECTION_CHANGE",
31
+ "begin": "2025-06-17",
32
+ "end": "2025-06-17",
33
+ "title": "AI Mode folded into GSC totals",
34
+ "metricImpact": ["impressions"],
35
+ "clicksAffected": false,
36
+ "impressionsAffected": true,
37
+ "positionAffected": false,
38
+ "source": "curated",
39
+ "notes": "AI Mode impressions folded into Search Console totals. Impressions step up."
40
+ },
41
+ {
42
+ "id": "gsc-impressions-fix-rollout-2026",
43
+ "kind": "DATA_ANOMALY",
44
+ "begin": "2026-04-03",
45
+ "end": "2026-04-27",
46
+ "title": "GSC impressions logging error correction rollout",
47
+ "metricImpact": ["impressions", "ctr", "position"],
48
+ "clicksAffected": false,
49
+ "impressionsAffected": true,
50
+ "positionAffected": true,
51
+ "source": "curated",
52
+ "notes": "Correction of the 2025-05-13 logging error, going forward only. Impressions drop as the inflation ends. No historical backfill."
53
+ },
54
+ {
55
+ "id": "core-2025-03",
56
+ "kind": "GOOGLE_RANKING_UPDATE",
57
+ "begin": "2025-03-13",
58
+ "end": "2025-03-27",
59
+ "title": "March 2025 core update",
60
+ "metricImpact": ["clicks", "impressions", "position"],
61
+ "clicksAffected": true,
62
+ "impressionsAffected": true,
63
+ "positionAffected": true,
64
+ "source": "curated"
65
+ },
66
+ {
67
+ "id": "core-2025-06",
68
+ "kind": "GOOGLE_RANKING_UPDATE",
69
+ "begin": "2025-06-30",
70
+ "end": "2025-07-17",
71
+ "title": "June 2025 core update",
72
+ "metricImpact": ["clicks", "impressions", "position"],
73
+ "clicksAffected": true,
74
+ "impressionsAffected": true,
75
+ "positionAffected": true,
76
+ "source": "curated"
77
+ },
78
+ {
79
+ "id": "spam-2025-08",
80
+ "kind": "GOOGLE_RANKING_UPDATE",
81
+ "begin": "2025-08-26",
82
+ "end": "2025-09-22",
83
+ "title": "August 2025 spam update",
84
+ "metricImpact": ["clicks", "impressions", "position"],
85
+ "clicksAffected": true,
86
+ "impressionsAffected": true,
87
+ "positionAffected": true,
88
+ "source": "curated"
89
+ },
90
+ {
91
+ "id": "core-2025-12",
92
+ "kind": "GOOGLE_RANKING_UPDATE",
93
+ "begin": "2025-12-11",
94
+ "end": "2025-12-29",
95
+ "title": "December 2025 core update",
96
+ "metricImpact": ["clicks", "impressions", "position"],
97
+ "clicksAffected": true,
98
+ "impressionsAffected": true,
99
+ "positionAffected": true,
100
+ "source": "curated"
101
+ },
102
+ {
103
+ "id": "discover-2026-02",
104
+ "kind": "GOOGLE_RANKING_UPDATE",
105
+ "begin": "2026-02-05",
106
+ "end": "2026-02-27",
107
+ "title": "February 2026 Discover update",
108
+ "metricImpact": ["clicks", "impressions"],
109
+ "clicksAffected": true,
110
+ "impressionsAffected": true,
111
+ "positionAffected": false,
112
+ "source": "curated"
113
+ },
114
+ {
115
+ "id": "spam-2026-03",
116
+ "kind": "GOOGLE_RANKING_UPDATE",
117
+ "begin": "2026-03-24",
118
+ "end": "2026-03-25",
119
+ "title": "March 2026 spam update",
120
+ "metricImpact": ["clicks", "impressions", "position"],
121
+ "clicksAffected": true,
122
+ "impressionsAffected": true,
123
+ "positionAffected": true,
124
+ "source": "curated"
125
+ },
126
+ {
127
+ "id": "core-2026-03",
128
+ "kind": "GOOGLE_RANKING_UPDATE",
129
+ "begin": "2026-03-27",
130
+ "end": "2026-04-08",
131
+ "title": "March 2026 core update",
132
+ "metricImpact": ["clicks", "impressions", "position"],
133
+ "clicksAffected": true,
134
+ "impressionsAffected": true,
135
+ "positionAffected": true,
136
+ "source": "curated"
137
+ },
138
+ {
139
+ "id": "core-2026-05",
140
+ "kind": "GOOGLE_RANKING_UPDATE",
141
+ "begin": "2026-05-21",
142
+ "end": "2026-06-02",
143
+ "title": "May 2026 core update",
144
+ "metricImpact": ["clicks", "impressions", "position"],
145
+ "clicksAffected": true,
146
+ "impressionsAffected": true,
147
+ "positionAffected": true,
148
+ "source": "curated"
149
+ },
150
+ {
151
+ "id": "spam-2026-06",
152
+ "kind": "GOOGLE_RANKING_UPDATE",
153
+ "begin": "2026-06-24",
154
+ "end": "2026-06-26",
155
+ "title": "June 2026 spam update",
156
+ "metricImpact": ["clicks", "impressions", "position"],
157
+ "clicksAffected": true,
158
+ "impressionsAffected": true,
159
+ "positionAffected": true,
160
+ "source": "curated"
161
+ },
162
+ {
163
+ "id": "spam-2026-08",
164
+ "kind": "GOOGLE_RANKING_UPDATE",
165
+ "begin": "2026-08-18",
166
+ "end": "2026-08-21",
167
+ "title": "August 2026 spam update",
168
+ "metricImpact": ["clicks", "impressions", "position"],
169
+ "clicksAffected": true,
170
+ "impressionsAffected": true,
171
+ "positionAffected": true,
172
+ "source": "curated"
173
+ }
174
+ ]
@@ -0,0 +1,17 @@
1
+ {
2
+ "gsc.searchAnalytics": {
3
+ "qpm": 1200,
4
+ "rowLimitMax": 25000,
5
+ "rowsPerDayPerType": 50000
6
+ },
7
+ "gsc.urlInspection": { "qpdPerSite": 2000, "qpmPerSite": 600 },
8
+ "gsc.other": { "qps": 20, "qpm": 200 },
9
+ "ga4.data": {
10
+ "tokensPerHourProjectProperty": 14000,
11
+ "tokensPerDayProperty": 200000,
12
+ "maxRows": 250000
13
+ },
14
+ "psi": { "qpd": 25000, "qpm": 240 },
15
+ "crux": { "qpm": 150, "purchasable": false },
16
+ "indexing": { "qpd": 200, "allowedTypes": ["JobPosting", "BroadcastEvent"] }
17
+ }
@@ -0,0 +1,46 @@
1
+ /**
2
+ * Adapted from OpenSEO `src/server/lib/gscErrors.ts` and `ga4Errors.ts` (MIT).
3
+ * Copyright (c) 2026 Ben Senescu and contributors.
4
+ *
5
+ * Workers/Better-Auth bindings stripped. Status-driven user-facing messages kept.
6
+ */
7
+ export declare class GscApiError extends Error {
8
+ readonly status: number;
9
+ readonly body?: string | undefined;
10
+ constructor(status: number, message: string, body?: string | undefined);
11
+ }
12
+ export declare class GscTokenError extends Error {
13
+ readonly cause?: unknown | undefined;
14
+ constructor(message: string, cause?: unknown | undefined);
15
+ }
16
+ export declare class GscNotConnectedError extends Error {
17
+ readonly siteId: string;
18
+ constructor(siteId: string);
19
+ }
20
+ export declare class Ga4AdminApiError extends Error {
21
+ readonly status: number;
22
+ constructor(status: number, message: string);
23
+ }
24
+ export declare class Ga4TokenError extends Error {
25
+ readonly cause?: unknown | undefined;
26
+ constructor(message: string, cause?: unknown | undefined);
27
+ }
28
+ export declare class Ga4DataApiError extends Error {
29
+ readonly status: number;
30
+ readonly retryAfterSeconds: number | null;
31
+ readonly upstreamReason: string | null;
32
+ constructor(status: number, message: string, retryAfterSeconds?: number | null, upstreamReason?: string | null);
33
+ }
34
+ export declare class Ga4NotConnectedError extends Error {
35
+ readonly siteId: string;
36
+ constructor(siteId: string);
37
+ }
38
+ export declare class QuotaExceededError extends Error {
39
+ readonly api: string;
40
+ readonly retryAfterMs: number;
41
+ constructor(api: string, message: string, retryAfterMs: number);
42
+ }
43
+ export declare function gscMessageForStatus(status: number, body: string): string;
44
+ export declare function ga4AdminMessageForStatus(status: number): string;
45
+ export declare function ga4DataMessageForStatus(status: number): string;
46
+ //# sourceMappingURL=errors.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"errors.d.ts","sourceRoot":"","sources":["../src/errors.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,qBAAa,WAAY,SAAQ,KAAK;aAElB,MAAM,EAAE,MAAM;aAEd,IAAI,CAAC,EAAE,MAAM;gBAFb,MAAM,EAAE,MAAM,EAC9B,OAAO,EAAE,MAAM,EACC,IAAI,CAAC,EAAE,MAAM,YAAA;CAKhC;AAED,qBAAa,aAAc,SAAQ,KAAK;aAGX,KAAK,CAAC,EAAE,OAAO;gBADxC,OAAO,EAAE,MAAM,EACU,KAAK,CAAC,EAAE,OAAO,YAAA;CAK3C;AAED,qBAAa,oBAAqB,SAAQ,KAAK;aACjB,MAAM,EAAE,MAAM;gBAAd,MAAM,EAAE,MAAM;CAI3C;AAED,qBAAa,gBAAiB,SAAQ,KAAK;aAEvB,MAAM,EAAE,MAAM;gBAAd,MAAM,EAAE,MAAM,EAC9B,OAAO,EAAE,MAAM;CAKlB;AAED,qBAAa,aAAc,SAAQ,KAAK;aAGX,KAAK,CAAC,EAAE,OAAO;gBADxC,OAAO,EAAE,MAAM,EACU,KAAK,CAAC,EAAE,OAAO,YAAA;CAK3C;AAED,qBAAa,eAAgB,SAAQ,KAAK;aAEtB,MAAM,EAAE,MAAM;aAEd,iBAAiB,EAAE,MAAM,GAAG,IAAI;aAChC,cAAc,EAAE,MAAM,GAAG,IAAI;gBAH7B,MAAM,EAAE,MAAM,EAC9B,OAAO,EAAE,MAAM,EACC,iBAAiB,GAAE,MAAM,GAAG,IAAW,EACvC,cAAc,GAAE,MAAM,GAAG,IAAW;CAKvD;AAED,qBAAa,oBAAqB,SAAQ,KAAK;aACjB,MAAM,EAAE,MAAM;gBAAd,MAAM,EAAE,MAAM;CAI3C;AAED,qBAAa,kBAAmB,SAAQ,KAAK;aAEzB,GAAG,EAAE,MAAM;aAEX,YAAY,EAAE,MAAM;gBAFpB,GAAG,EAAE,MAAM,EAC3B,OAAO,EAAE,MAAM,EACC,YAAY,EAAE,MAAM;CAKvC;AAED,wBAAgB,mBAAmB,CAAC,MAAM,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG,MAAM,CAWxE;AAED,wBAAgB,wBAAwB,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,CAO/D;AAED,wBAAgB,uBAAuB,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,CAK9D"}