@gscdump/sdk 1.0.2 → 1.0.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (52) hide show
  1. package/README.md +7 -1
  2. package/dist/_chunks/errors.d.mts +22 -0
  3. package/dist/_chunks/request.d.mts +25 -0
  4. package/dist/_chunks/request.mjs +84 -0
  5. package/dist/_chunks/search-console-signals.mjs +9 -0
  6. package/dist/analytics-client.d.mts +53 -0
  7. package/dist/analytics-client.mjs +128 -0
  8. package/dist/analyzer-defs.d.mts +73 -0
  9. package/dist/analyzer-defs.mjs +4 -0
  10. package/dist/anonymization.d.mts +6 -0
  11. package/dist/anonymization.mjs +12 -0
  12. package/dist/archetype-compile.d.mts +48 -0
  13. package/dist/archetype-compile.mjs +124 -0
  14. package/dist/client.d.mts +122 -0
  15. package/dist/client.mjs +365 -0
  16. package/dist/country-names.d.mts +2 -0
  17. package/dist/country-names.mjs +56 -0
  18. package/dist/cwv-thresholds.d.mts +13 -0
  19. package/dist/cwv-thresholds.mjs +23 -0
  20. package/dist/errors.d.mts +2 -0
  21. package/dist/errors.mjs +47 -0
  22. package/dist/gsc-console-url.d.mts +12 -0
  23. package/dist/gsc-console-url.mjs +15 -0
  24. package/dist/gsc-constants.d.mts +17 -0
  25. package/dist/gsc-constants.mjs +2 -0
  26. package/dist/gsc-error.d.mts +12 -0
  27. package/dist/gsc-error.mjs +42 -0
  28. package/dist/gsc-period-presets.d.mts +31 -0
  29. package/dist/gsc-period-presets.mjs +115 -0
  30. package/dist/gsc-rows.d.mts +54 -0
  31. package/dist/gsc-rows.mjs +48 -0
  32. package/dist/hosted-query.d.mts +31 -0
  33. package/dist/hosted-query.mjs +105 -0
  34. package/dist/index.d.mts +21 -852
  35. package/dist/index.mjs +21 -2281
  36. package/dist/indexing-issues.d.mts +29 -0
  37. package/dist/indexing-issues.mjs +168 -0
  38. package/dist/lifecycle.d.mts +14 -0
  39. package/dist/lifecycle.mjs +84 -0
  40. package/dist/period.d.mts +49 -0
  41. package/dist/period.mjs +138 -0
  42. package/dist/search-console-stage.d.mts +102 -0
  43. package/dist/search-console-stage.mjs +292 -0
  44. package/dist/site-baseline.d.mts +69 -0
  45. package/dist/site-baseline.mjs +112 -0
  46. package/dist/site-triage.d.mts +86 -0
  47. package/dist/site-triage.mjs +268 -0
  48. package/dist/v1/index.d.mts +7 -1
  49. package/dist/v1/index.mjs +8 -2
  50. package/dist/webhook.d.mts +29 -0
  51. package/dist/webhook.mjs +118 -0
  52. package/package.json +110 -5
package/README.md CHANGED
@@ -34,6 +34,12 @@ The root `createPartnerClient` export remains only for operations still in the
34
34
  compatibility inventory. Do not use it for an operation already available from
35
35
  `@gscdump/sdk/v1`.
36
36
 
37
+ The root is also a compatibility aggregate for the pure helper modules. New
38
+ code should use the focused subpaths, including `period`, `period-presets`,
39
+ `site-triage`, `site-baseline`, `search-console-stage`, `indexing-issues`,
40
+ `analytics`, `partner`, `partner-errors`, `lifecycle`, `webhook`, and
41
+ `gsc-console-url`.
42
+
37
43
  For example, a typed hosted report query is:
38
44
 
39
45
  ```ts
@@ -97,7 +103,7 @@ signature, and normalize event names. gscdump.com owns webhook production and
97
103
  delivery.
98
104
 
99
105
  ```ts
100
- import { parseWebhookPayload } from '@gscdump/sdk'
106
+ import { parseWebhookPayload } from '@gscdump/sdk/webhook'
101
107
 
102
108
  const envelope = await parseWebhookPayload(rawJson, {
103
109
  secret: webhookSecret,
@@ -0,0 +1,22 @@
1
+ type PartnerErrorKind = 'auth' | 'rate-limit' | 'provisioning' | 'permission' | 'network' | 'validation' | 'not-found' | 'server' | 'unknown';
2
+ interface PartnerErrorInfo {
3
+ kind: PartnerErrorKind;
4
+ statusCode?: number;
5
+ message: string;
6
+ data?: unknown;
7
+ }
8
+ declare class PartnerApiError extends Error {
9
+ readonly kind: PartnerErrorKind;
10
+ readonly statusCode?: number;
11
+ readonly data?: unknown;
12
+ constructor(info: PartnerErrorInfo);
13
+ }
14
+ declare function toPartnerError(error: unknown): PartnerApiError;
15
+ /**
16
+ * Re-raise a modelled `PartnerApiError` as itself. The error variant of every
17
+ * `*Result` core already IS the throwable `PartnerApiError`, so the throwing
18
+ * wrappers preserve the exact identity/message existing call sites and tests
19
+ * assert (`rejects.toThrow(PartnerApiError)`, `toThrow('Invalid webhook signature')`).
20
+ */
21
+ declare function partnerErrorToException(error: PartnerApiError): PartnerApiError;
22
+ export { PartnerApiError, PartnerErrorInfo, PartnerErrorKind, partnerErrorToException, toPartnerError };
@@ -0,0 +1,25 @@
1
+ import { PartnerApiError } from "./errors.mjs";
2
+ import { Result } from "gscdump/result";
3
+ import { ZodTypeAny } from "zod";
4
+ type HostedFetch = <T = unknown>(request: string, options?: HostedFetchOptions) => Promise<T>;
5
+ type HostedHeaders = HeadersInit | (() => HeadersInit | Promise<HeadersInit>);
6
+ interface HostedFetchOptions {
7
+ method?: string;
8
+ headers?: HeadersInit;
9
+ query?: Record<string, unknown>;
10
+ body?: unknown;
11
+ /** Opt into in-flight de-dupe for safe read-style POST requests. */
12
+ dedupe?: boolean;
13
+ [key: string]: unknown;
14
+ }
15
+ interface HostedClientOptions {
16
+ apiBase?: string;
17
+ fetch?: HostedFetch;
18
+ headers?: HostedHeaders;
19
+ validate?: boolean | 'request' | 'response';
20
+ /** Coalesce concurrent identical GET/HEAD requests within this client instance. */
21
+ dedupe?: boolean;
22
+ /** Optional shared in-flight map for de-duping across related client instances. */
23
+ dedupeScope?: Map<string, Promise<Result<unknown, PartnerApiError>>>;
24
+ }
25
+ export { HostedClientOptions, HostedFetch, HostedFetchOptions, HostedHeaders };
@@ -0,0 +1,84 @@
1
+ import { partnerErrorToException, toPartnerError } from "../errors.mjs";
2
+ import { err, ok, unwrapResult } from "gscdump/result";
3
+ import { ofetch } from "ofetch";
4
+ const TRAILING_SLASH_RE = /\/+$/;
5
+ const LEADING_SLASH_RE = /^\/+/;
6
+ function trimApiBase(apiBase, fallback) {
7
+ return (apiBase ?? fallback).replace(TRAILING_SLASH_RE, "");
8
+ }
9
+ function buildPath(apiBase, path) {
10
+ if (!apiBase) return path.startsWith("/") ? path : `/${path}`;
11
+ return `${apiBase}/${path.replace(LEADING_SLASH_RE, "")}`;
12
+ }
13
+ function mergeHeaders(base, extra) {
14
+ const headers = new Headers(base);
15
+ if (extra) for (const [key, value] of new Headers(extra).entries()) headers.set(key, value);
16
+ return headers;
17
+ }
18
+ async function resolveHeaders(options) {
19
+ const resolved = typeof options.headers === "function" ? await options.headers() : options.headers;
20
+ if (!options.apiKey) return resolved;
21
+ return mergeHeaders(resolved, { "x-api-key": options.apiKey });
22
+ }
23
+ function shouldValidate(options, phase) {
24
+ return options.validate === true || options.validate === phase;
25
+ }
26
+ function parseWith(schema, value) {
27
+ return schema ? schema.parse(value) : value;
28
+ }
29
+ function stableJson(value) {
30
+ if (value == null || typeof value !== "object") return JSON.stringify(value) ?? "undefined";
31
+ if (Array.isArray(value)) return `[${value.map((item) => item === void 0 ? "undefined" : stableJson(item)).join(",")}]`;
32
+ return `{${Object.entries(value).filter(([, item]) => item !== void 0).sort(([a], [b]) => a.localeCompare(b)).map(([key, item]) => `${JSON.stringify(key)}:${stableJson(item)}`).join(",")}}`;
33
+ }
34
+ function headersKey(headers) {
35
+ return [...headers.entries()].sort(([a], [b]) => a.localeCompare(b)).map(([key, value]) => `${key}:${value}`).join("\n");
36
+ }
37
+ function isDedupeable(init) {
38
+ if ("signal" in init && init.signal) return false;
39
+ const method = (init.method ?? "GET").toUpperCase();
40
+ return method === "GET" || method === "HEAD" || init.dedupe === true;
41
+ }
42
+ function createHostedRequester(options, defaults) {
43
+ const fetchImpl = options.fetch ?? ofetch;
44
+ const apiBase = trimApiBase(options.apiBase, defaults.apiBase);
45
+ const dedupe = options.dedupe !== false;
46
+ const inflight = options.dedupeScope ?? /* @__PURE__ */ new Map();
47
+ async function requestResult(path, init = {}, responseSchema) {
48
+ const { dedupe: _dedupe, ...fetchInit } = init;
49
+ const headers = mergeHeaders(await resolveHeaders(options), init.headers);
50
+ const fullPath = buildPath(apiBase, path);
51
+ const dedupeKey = dedupe && isDedupeable(init) ? `${(init.method ?? "GET").toUpperCase()} ${fullPath}\nq=${stableJson(init.query)}\nb=${stableJson(init.body)}\nh=${headersKey(headers)}` : null;
52
+ if (dedupeKey) {
53
+ const existing = inflight.get(dedupeKey);
54
+ if (existing) return existing;
55
+ }
56
+ const run = (async () => {
57
+ try {
58
+ const out = await fetchImpl(fullPath, {
59
+ ...fetchInit,
60
+ headers
61
+ });
62
+ return ok(shouldValidate(options, "response") ? parseWith(responseSchema, out) : out);
63
+ } catch (error) {
64
+ return err(toPartnerError(error));
65
+ }
66
+ })();
67
+ if (dedupeKey) {
68
+ inflight.set(dedupeKey, run);
69
+ run.finally(() => {
70
+ if (inflight.get(dedupeKey) === run) inflight.delete(dedupeKey);
71
+ });
72
+ }
73
+ return run;
74
+ }
75
+ async function request(path, init = {}, responseSchema) {
76
+ return unwrapResult(await requestResult(path, init, responseSchema), partnerErrorToException);
77
+ }
78
+ return {
79
+ request,
80
+ requestResult,
81
+ shouldValidate: (phase) => shouldValidate(options, phase)
82
+ };
83
+ }
84
+ export { createHostedRequester };
@@ -0,0 +1,9 @@
1
+ function formatSearchConsoleCount(value) {
2
+ return new Intl.NumberFormat("en").format(Math.max(0, Math.round(value)));
3
+ }
4
+ function countSearchConsoleIssues(issues, ...types) {
5
+ if (!issues?.length) return 0;
6
+ const wanted = new Set(types);
7
+ return issues.reduce((sum, issue) => sum + (wanted.has(issue.type) ? issue.count : 0), 0);
8
+ }
9
+ export { countSearchConsoleIssues, formatSearchConsoleCount };
@@ -0,0 +1,53 @@
1
+ import { AnalysisSourcesOptions, SearchTypeOptions, SourceRangeOptions } from "./hosted-query.mjs";
2
+ import { HostedClientOptions, HostedFetch, HostedFetchOptions, HostedHeaders } from "./_chunks/request.mjs";
3
+ import { AnalysisSourcesResponse, BackfillRange, BackfillResponse, BulkFileResolutionRequest, BulkFileResolutionResponse, CountriesResponse, IndexingDiagnostics, IndexingDiagnosticsParams, IndexingInspectRateLimited, IndexingInspectRequest, IndexingInspectResponse, IndexingUrlStatus, IndexingUrlsResponse, InspectionHistoryResponse, InspectionIndex, QueryDimSourceResponse, RollupEnvelope, SearchAppearanceResponse, SiteListItem, SitemapChangesResponse, SitemapHistoryResponse, SitemapIndex, SourceInfoOptions, SourceInfoResponse, WhoamiResponse } from "@gscdump/contracts";
4
+ type AnalyticsFetch = HostedFetch;
5
+ type AnalyticsHeaders = HostedHeaders;
6
+ type AnalyticsFetchOptions = HostedFetchOptions;
7
+ interface AnalyticsClientOptions extends HostedClientOptions {
8
+ apiBase?: string;
9
+ fetch?: AnalyticsFetch;
10
+ headers?: AnalyticsHeaders;
11
+ validate?: boolean | 'request' | 'response';
12
+ }
13
+ /** Hosted analytics transport exposed by `@gscdump/sdk`. */
14
+ interface AnalyticsClient {
15
+ whoami: () => Promise<WhoamiResponse>;
16
+ listSites: () => Promise<SiteListItem[]>;
17
+ getBulkSources: (params: BulkFileResolutionRequest) => Promise<BulkFileResolutionResponse>;
18
+ getSourceInfo: (siteId: string, options?: SourceInfoOptions) => Promise<SourceInfoResponse>;
19
+ getAnalysisSources: (siteId: string, tables?: string[] | string | AnalysisSourcesOptions, options?: SearchTypeOptions & SourceRangeOptions) => Promise<AnalysisSourcesResponse>;
20
+ getQueryDimSource: (siteId: string) => Promise<QueryDimSourceResponse>;
21
+ analyze: <T = unknown>(siteId: string, params: unknown) => Promise<T>;
22
+ getRollup: <T = unknown>(siteId: string, rollupId: string, params?: {
23
+ start?: string;
24
+ end?: string;
25
+ }) => Promise<RollupEnvelope<T>>;
26
+ requestBackfill: (siteId: string, range: BackfillRange) => Promise<BackfillResponse>;
27
+ getSitemaps: (siteId: string) => Promise<SitemapIndex>;
28
+ getSitemapHistory: (siteId: string, hash: string) => Promise<SitemapHistoryResponse>;
29
+ getSitemapChanges: (siteId: string, params?: {
30
+ days?: number;
31
+ }) => Promise<SitemapChangesResponse>;
32
+ getInspections: (siteId: string) => Promise<InspectionIndex>;
33
+ getInspectionHistory: (siteId: string, hash: string) => Promise<InspectionHistoryResponse>;
34
+ getIndexingUrls: (siteId: string, params?: {
35
+ limit?: number;
36
+ offset?: number;
37
+ status?: IndexingUrlStatus;
38
+ issue?: string;
39
+ search?: string;
40
+ }) => Promise<IndexingUrlsResponse>;
41
+ getIndexingDiagnostics: (siteId: string, params?: IndexingDiagnosticsParams) => Promise<IndexingDiagnostics>;
42
+ requestIndexingInspect: (siteId: string, body: IndexingInspectRequest) => Promise<IndexingInspectResponse | IndexingInspectRateLimited>;
43
+ getCountries: (siteId: string, range: {
44
+ start: string;
45
+ end: string;
46
+ }) => Promise<CountriesResponse>;
47
+ getSearchAppearance: (siteId: string, range: {
48
+ start: string;
49
+ end: string;
50
+ }) => Promise<SearchAppearanceResponse>;
51
+ }
52
+ declare function createAnalyticsClient(options?: AnalyticsClientOptions): AnalyticsClient;
53
+ export { AnalyticsClient, AnalyticsClientOptions, AnalyticsFetch, AnalyticsFetchOptions, AnalyticsHeaders, createAnalyticsClient };
@@ -0,0 +1,128 @@
1
+ import { indexingDiagnosticsQuery, indexingUrlsQuery, sourceInfoQuery, tablesQuery, withDefaultSearchType } from "./hosted-query.mjs";
2
+ import { createHostedRequester } from "./_chunks/request.mjs";
3
+ import { analyticsEndpoints } from "@gscdump/contracts/analytics";
4
+ function createAnalyticsClient(options = {}) {
5
+ const { request, shouldValidate } = createHostedRequester(options, { apiBase: "" });
6
+ return {
7
+ whoami() {
8
+ const endpoint = analyticsEndpoints.whoami;
9
+ return request(endpoint.path, { method: endpoint.method }, endpoint.response);
10
+ },
11
+ listSites() {
12
+ const endpoint = analyticsEndpoints.listSites;
13
+ return request(endpoint.path, { method: endpoint.method }, endpoint.response);
14
+ },
15
+ getBulkSources(params) {
16
+ const endpoint = analyticsEndpoints.getBulkSources;
17
+ const { siteIds, tables, ...options } = params;
18
+ const query = tablesQuery(tables, options);
19
+ if (siteIds?.length) query.siteIds = [...new Set(siteIds.filter(Boolean))].join(",");
20
+ return request(endpoint.path, {
21
+ method: endpoint.method,
22
+ query
23
+ }, endpoint.response);
24
+ },
25
+ getSourceInfo(siteId, options) {
26
+ const endpoint = analyticsEndpoints.getSourceInfo;
27
+ return request(endpoint.path(siteId), {
28
+ method: endpoint.method,
29
+ query: sourceInfoQuery(options)
30
+ }, endpoint.response);
31
+ },
32
+ getAnalysisSources(siteId, tables, options) {
33
+ const endpoint = analyticsEndpoints.getAnalysisSources;
34
+ return request(endpoint.path(siteId), {
35
+ method: endpoint.method,
36
+ query: tablesQuery(tables, options)
37
+ }, endpoint.response);
38
+ },
39
+ getQueryDimSource(siteId) {
40
+ const endpoint = analyticsEndpoints.getQueryDimSource;
41
+ return request(endpoint.path(siteId), { method: endpoint.method }, endpoint.response);
42
+ },
43
+ analyze(siteId, params) {
44
+ const endpoint = analyticsEndpoints.analyze;
45
+ return request(endpoint.path(siteId), {
46
+ method: endpoint.method,
47
+ body: withDefaultSearchType(params),
48
+ dedupe: true
49
+ });
50
+ },
51
+ getRollup(siteId, rollupId, params) {
52
+ const endpoint = analyticsEndpoints.getRollup;
53
+ return request(endpoint.path(siteId, rollupId), {
54
+ method: endpoint.method,
55
+ query: params
56
+ }, endpoint.response);
57
+ },
58
+ requestBackfill(siteId, range) {
59
+ const endpoint = analyticsEndpoints.requestBackfill;
60
+ const body = shouldValidate("request") ? endpoint.body.parse(range) : range;
61
+ return request(endpoint.path(siteId), {
62
+ method: endpoint.method,
63
+ body
64
+ }, endpoint.response);
65
+ },
66
+ getSitemaps(siteId) {
67
+ const endpoint = analyticsEndpoints.getSitemaps;
68
+ return request(endpoint.path(siteId), { method: endpoint.method }, endpoint.response);
69
+ },
70
+ getSitemapHistory(siteId, hash) {
71
+ const endpoint = analyticsEndpoints.getSitemapHistory;
72
+ return request(endpoint.path(siteId, hash), { method: endpoint.method }, endpoint.response);
73
+ },
74
+ getSitemapChanges(siteId, params = {}) {
75
+ const endpoint = analyticsEndpoints.getSitemapChanges;
76
+ return request(endpoint.path(siteId), {
77
+ method: endpoint.method,
78
+ query: params
79
+ }, endpoint.response);
80
+ },
81
+ getInspections(siteId) {
82
+ const endpoint = analyticsEndpoints.getInspections;
83
+ return request(endpoint.path(siteId), { method: endpoint.method }, endpoint.response);
84
+ },
85
+ getInspectionHistory(siteId, hash) {
86
+ const endpoint = analyticsEndpoints.getInspectionHistory;
87
+ return request(endpoint.path(siteId, hash), { method: endpoint.method }, endpoint.response);
88
+ },
89
+ getIndexingUrls(siteId, params = {}) {
90
+ const endpoint = analyticsEndpoints.getIndexingUrls;
91
+ return request(endpoint.path(siteId), {
92
+ method: endpoint.method,
93
+ query: indexingUrlsQuery(params)
94
+ }, endpoint.response);
95
+ },
96
+ getIndexingDiagnostics(siteId, params = {}) {
97
+ const endpoint = analyticsEndpoints.getIndexingDiagnostics;
98
+ const parsed = shouldValidate("request") ? endpoint.query.parse(params) : params;
99
+ return request(endpoint.path(siteId), {
100
+ method: endpoint.method,
101
+ query: indexingDiagnosticsQuery(parsed)
102
+ }, endpoint.response);
103
+ },
104
+ requestIndexingInspect(siteId, body) {
105
+ const endpoint = analyticsEndpoints.requestIndexingInspect;
106
+ const parsed = shouldValidate("request") ? endpoint.body.parse(body) : body;
107
+ return request(endpoint.path(siteId), {
108
+ method: endpoint.method,
109
+ body: parsed
110
+ }, endpoint.response);
111
+ },
112
+ getCountries(siteId, range) {
113
+ const endpoint = analyticsEndpoints.getCountries;
114
+ return request(endpoint.path(siteId), {
115
+ method: endpoint.method,
116
+ query: range
117
+ }, endpoint.response);
118
+ },
119
+ getSearchAppearance(siteId, range) {
120
+ const endpoint = analyticsEndpoints.getSearchAppearance;
121
+ return request(endpoint.path(siteId), {
122
+ method: endpoint.method,
123
+ query: range
124
+ }, endpoint.response);
125
+ }
126
+ };
127
+ }
128
+ export { createAnalyticsClient };
@@ -0,0 +1,73 @@
1
+ import { ActionSource } from "@gscdump/analysis";
2
+ import { AnalysisTool } from "@gscdump/engine/analysis-types";
3
+ type GscAnalyzerKind = 'analyzer' | 'semantic' | 'action';
4
+ type GscAnalyzerAccent = 'primary' | 'warning' | 'success' | 'error' | 'neutral';
5
+ interface GscAnalyzerInsightCard {
6
+ icon: string;
7
+ accent: GscAnalyzerAccent;
8
+ description: string;
9
+ summarize: (res: {
10
+ results: unknown[];
11
+ meta: Record<string, unknown>;
12
+ }) => {
13
+ headline: string;
14
+ tagline: string;
15
+ };
16
+ }
17
+ interface GscAnalyzerStatTile {
18
+ label: string;
19
+ value: string | number;
20
+ /** Optional CSS color for the value (e.g. improved/worsened in change-points). */
21
+ valueColor?: string;
22
+ }
23
+ interface GscAnalyzerPanelResult {
24
+ results: unknown[];
25
+ meta: Record<string, unknown>;
26
+ queryMs?: number | null;
27
+ }
28
+ interface GscAnalyzerPanelSpec<TComponent = unknown> {
29
+ /**
30
+ * Body component. Receives `{ rows, meta, range }` as props. Lazy-import
31
+ * to preserve route-level codesplitting.
32
+ *
33
+ * Typed `unknown` in the SDK; consumers can narrow via generic
34
+ * (`GscAnalyzerDefinition<VueComponent>`).
35
+ */
36
+ component: TComponent;
37
+ /** Project a result to the header stat tiles. Empty array = no tiles. */
38
+ summarize?: (res: GscAnalyzerPanelResult) => GscAnalyzerStatTile[];
39
+ /** Italic footer caption explaining the underlying method. */
40
+ caption?: string;
41
+ /**
42
+ * When true, the shell skips its loading/error/empty gating and renders
43
+ * the body component immediately (pipeline panels: action priority, content gap).
44
+ */
45
+ ownsLifecycle?: boolean;
46
+ }
47
+ interface GscAnalyzerCapabilities<TComponent = unknown> {
48
+ /** Opt into the `/insights` curated grid by providing a card config. */
49
+ insightCard?: GscAnalyzerInsightCard;
50
+ /** Opt into `useActionPriority`. The value is the typed `ActionSource` slug. */
51
+ actionPriority?: ActionSource;
52
+ /**
53
+ * Opt into the unified `/analyze` panel renderer. Without this, the tab
54
+ * falls back to the generic table renderer.
55
+ */
56
+ panel?: GscAnalyzerPanelSpec<TComponent>;
57
+ }
58
+ type GscAnalyzerCapability = keyof GscAnalyzerCapabilities;
59
+ interface GscAnalyzerDefinition<TComponent = unknown> {
60
+ /** Stable analyzer id. Must match an `AnalysisTool` slug for built-in analyzers. */
61
+ id: AnalysisTool | (string & {});
62
+ label: string;
63
+ kind: GscAnalyzerKind;
64
+ /** Reads the per-query table; sums silently drop GSC-anonymized impressions. */
65
+ isQueryGrained?: boolean;
66
+ /** Capability opt-ins. Each key gates a downstream consumer. */
67
+ capabilities?: GscAnalyzerCapabilities<TComponent>;
68
+ }
69
+ type GscAnalyzerDefinitionWithCapability<K extends GscAnalyzerCapability, TComponent = unknown> = GscAnalyzerDefinition<TComponent> & {
70
+ capabilities: { [P in K]: NonNullable<GscAnalyzerCapabilities<TComponent>[P]>; };
71
+ };
72
+ declare function defineGscAnalyzer<TComponent = unknown>(def: GscAnalyzerDefinition<TComponent>): GscAnalyzerDefinition<TComponent>;
73
+ export { GscAnalyzerAccent, GscAnalyzerCapabilities, GscAnalyzerCapability, GscAnalyzerDefinition, GscAnalyzerDefinitionWithCapability, GscAnalyzerInsightCard, GscAnalyzerKind, GscAnalyzerPanelResult, GscAnalyzerPanelSpec, GscAnalyzerStatTile, defineGscAnalyzer };
@@ -0,0 +1,4 @@
1
+ function defineGscAnalyzer(def) {
2
+ return def;
3
+ }
4
+ export { defineGscAnalyzer };
@@ -0,0 +1,6 @@
1
+ interface DailyAnonInput {
2
+ impressions: number;
3
+ anonymizedImpressionsPct: number;
4
+ }
5
+ declare function weightedAnonPct(days: readonly DailyAnonInput[] | null | undefined, window?: number): number | null;
6
+ export { DailyAnonInput, weightedAnonPct };
@@ -0,0 +1,12 @@
1
+ function weightedAnonPct(days, window = 28) {
2
+ if (!days?.length) return null;
3
+ const trailing = days.slice(-window);
4
+ let totalImpressions = 0;
5
+ let weighted = 0;
6
+ for (const d of trailing) {
7
+ totalImpressions += d.impressions;
8
+ weighted += d.impressions * d.anonymizedImpressionsPct;
9
+ }
10
+ return totalImpressions > 0 ? weighted / totalImpressions : null;
11
+ }
12
+ export { weightedAnonPct };
@@ -0,0 +1,48 @@
1
+ import { BuilderState } from "gscdump/query";
2
+ import { ArchetypeQuery, GscSearchType, WireDateRange } from "@gscdump/contracts";
3
+ /**
4
+ * Extract the `{ start, end }` date window from a builder-state filter in
5
+ * either accepted format (SDK-branded or partner wire). Returns `null` when no
6
+ * complete range is present.
7
+ */
8
+ declare function extractWireDateRange(filter: unknown): WireDateRange | null;
9
+ interface BuilderStateToArchetypeOptions {
10
+ searchType?: GscSearchType;
11
+ compareRange?: WireDateRange;
12
+ }
13
+ /**
14
+ * Translate a `BuilderState` into the best-fit `ArchetypeQuery`, or `null`
15
+ * when the shape can't be expressed (the caller keeps its legacy path — this
16
+ * function never throws).
17
+ *
18
+ * Supported shapes (5 of the archetypes — the others are either caller-driven
19
+ * builders never reached via a BuilderState, `arbitrary-sql`, or
20
+ * `aux-cloud-only`):
21
+ * - `dimensions: ['date']`, no entity filter → site-daily-timeseries
22
+ * - `dimensions: ['date']`, one page/query/queryCanonical
23
+ * equality → entity-daily-timeseries
24
+ * - `dimensions: ['date', X]` → multi-series-stacked-daily
25
+ * - `dimensions: [X]` (non-date) → top-n-breakdown (extra
26
+ * equality filters on OTHER dimensions become `facets`)
27
+ * - `dimensions: ['page','query']` (either order) → two-dimension-detail
28
+ *
29
+ * Everything else — 0 or 3+ dimensions, no date range, an OR group, a
30
+ * non-equality predicate the target SQL builder can't apply, a
31
+ * `two-dimension-detail` pair that isn't exactly {page, query} (the SQL is
32
+ * hardcoded to `url, query` regardless of which two dims were requested) —
33
+ * returns `null`.
34
+ */
35
+ declare function builderStateToArchetype(siteId: string, state: BuilderState, opts?: BuilderStateToArchetypeOptions): ArchetypeQuery | null;
36
+ /**
37
+ * Archetypes a hosted seam's execution wrapper can safely serve end-to-end
38
+ * today (rows + an ACCURATE `totalCount`/`totals`, matching the legacy
39
+ * `R2QueryResult` contract). `builderStateToArchetype` above can produce a
40
+ * `two-dimension-detail` query, but `TwoDimensionDetailQuery`/
41
+ * `buildTwoDimensionDetail` support neither `offset` nor `includeTotal` —
42
+ * there is no way to get an exact grand total or paginate past page 1 through
43
+ * the dispatcher yet, so seam wrappers decline it (same as a compile miss)
44
+ * rather than serve a wrong `totalCount`. Revisit once the contract grows
45
+ * those fields.
46
+ */
47
+ declare function archetypeSeamSupportsQuery(query: ArchetypeQuery): boolean;
48
+ export { BuilderStateToArchetypeOptions, archetypeSeamSupportsQuery, builderStateToArchetype, extractWireDateRange };
@@ -0,0 +1,124 @@
1
+ import { entityDailyTimeseries, multiSeriesStackedDaily, siteDailyTimeseries, topNBreakdown, twoDimensionDetail } from "@gscdump/contracts/archetypes";
2
+ import { extractDateRange, normalizeFilter } from "gscdump/query";
3
+ const KNOWN_DIMENSIONS = /* @__PURE__ */ new Set([
4
+ "query",
5
+ "queryCanonical",
6
+ "page",
7
+ "country",
8
+ "device",
9
+ "searchAppearance",
10
+ "date",
11
+ "hour"
12
+ ]);
13
+ const DATE_RANGE_OPERATORS = /* @__PURE__ */ new Set([
14
+ "between",
15
+ "gte",
16
+ "gt",
17
+ "lte",
18
+ "lt"
19
+ ]);
20
+ const EQUALITY_OPERATORS = /* @__PURE__ */ new Set(["equals", "eq"]);
21
+ const ENTITY_DIMENSIONS = [
22
+ "page",
23
+ "query",
24
+ "queryCanonical"
25
+ ];
26
+ function collectEqualityMatches(filter, out) {
27
+ if (!filter) return true;
28
+ if (filter._groupType === "or") return false;
29
+ for (const f of filter._filters) {
30
+ if (f.dimension === "date") {
31
+ if (!DATE_RANGE_OPERATORS.has(f.operator)) return false;
32
+ continue;
33
+ }
34
+ if (!EQUALITY_OPERATORS.has(f.operator) || !KNOWN_DIMENSIONS.has(f.dimension)) return false;
35
+ const dim = f.dimension;
36
+ const existing = out.get(dim);
37
+ if (existing !== void 0 && existing !== f.expression) return false;
38
+ out.set(dim, f.expression);
39
+ }
40
+ for (const nested of filter._nestedGroups ?? []) if (!collectEqualityMatches(nested, out)) return false;
41
+ return true;
42
+ }
43
+ function extractWireDateRange(filter) {
44
+ const { startDate, endDate } = extractDateRange(filter);
45
+ return startDate && endDate ? {
46
+ start: startDate,
47
+ end: endDate
48
+ } : null;
49
+ }
50
+ function builderStateToArchetype(siteId, state, opts = {}) {
51
+ const range = extractWireDateRange(state.filter);
52
+ if (!range) return null;
53
+ const matches = /* @__PURE__ */ new Map();
54
+ if (!collectEqualityMatches(normalizeFilter(state.filter), matches)) return null;
55
+ const dims = state.dimensions ?? [];
56
+ if (dims.some((d) => !KNOWN_DIMENSIONS.has(d))) return null;
57
+ if (dims.includes("hour") || dims.includes("searchAppearance")) return null;
58
+ const searchType = opts.searchType;
59
+ const cmp = opts.compareRange;
60
+ const order = state.orderBy ? {
61
+ metric: state.orderBy.column,
62
+ dir: state.orderBy.dir
63
+ } : void 0;
64
+ if (dims.length === 1 && dims[0] === "date") {
65
+ const entityDims = ENTITY_DIMENSIONS.filter((c) => matches.has(c));
66
+ if (entityDims.length > 1) return null;
67
+ if (entityDims.length === 1) {
68
+ const dim = entityDims[0];
69
+ if (matches.size > 1) return null;
70
+ return entityDailyTimeseries(siteId, range, {
71
+ dimension: dim,
72
+ value: matches.get(dim)
73
+ }, {
74
+ searchType,
75
+ compareRange: cmp
76
+ });
77
+ }
78
+ if (matches.size > 0) return null;
79
+ return siteDailyTimeseries(siteId, range, {
80
+ searchType,
81
+ compareRange: cmp
82
+ });
83
+ }
84
+ if (dims.length === 2 && dims.includes("date")) {
85
+ if (matches.size > 0) return null;
86
+ return multiSeriesStackedDaily(siteId, range, dims.find((d) => d !== "date"), {
87
+ searchType,
88
+ compareRange: cmp
89
+ });
90
+ }
91
+ if (dims.length === 1 && dims[0] !== "date") {
92
+ const primary = dims[0];
93
+ if (matches.has(primary)) return null;
94
+ const facets = [...matches.entries()].map(([column, value]) => ({
95
+ column,
96
+ op: "eq",
97
+ value
98
+ }));
99
+ return topNBreakdown(siteId, range, primary, {
100
+ searchType,
101
+ compareRange: cmp,
102
+ ...order ? { orderBy: order } : {},
103
+ limit: state.rowLimit ?? 50,
104
+ ...state.startRow ? { offset: state.startRow } : {},
105
+ ...facets.length ? { facets } : {}
106
+ });
107
+ }
108
+ if (dims.length === 2 && !dims.includes("date")) {
109
+ const set = new Set(dims);
110
+ if (!set.has("page") || !set.has("query")) return null;
111
+ if (matches.size > 0) return null;
112
+ return twoDimensionDetail(siteId, range, {
113
+ searchType,
114
+ compareRange: cmp,
115
+ ...order ? { orderBy: order } : {},
116
+ ...state.rowLimit ? { limit: state.rowLimit } : {}
117
+ });
118
+ }
119
+ return null;
120
+ }
121
+ function archetypeSeamSupportsQuery(query) {
122
+ return query.archetype !== "two-dimension-detail";
123
+ }
124
+ export { archetypeSeamSupportsQuery, builderStateToArchetype, extractWireDateRange };