@gscdump/nuxt 0.22.1 → 1.5.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (65) hide show
  1. package/README.md +10 -125
  2. package/dist/module.d.mts +22 -0
  3. package/dist/module.mjs +30 -0
  4. package/dist/runtime-config.d.mts +12 -0
  5. package/dist/runtime-config.mjs +27 -0
  6. package/package.json +25 -44
  7. package/app/assets/css/main.css +0 -2
  8. package/app/components/GscAnalyzerPanel.vue +0 -94
  9. package/app/components/GscAnonymizationBanner.vue +0 -46
  10. package/app/components/GscBootProgress.vue +0 -297
  11. package/app/components/GscCommandPalette.vue +0 -77
  12. package/app/components/GscDashboardPage.vue +0 -26
  13. package/app/components/GscEngineBadge.vue +0 -28
  14. package/app/components/GscHero.vue +0 -215
  15. package/app/components/GscLazyTopResult.vue +0 -45
  16. package/app/components/GscPerformanceChart.vue +0 -532
  17. package/app/components/GscPositionDistributionChart.vue +0 -63
  18. package/app/components/GscQueryLabel.vue +0 -63
  19. package/app/components/GscSitePageHeader.vue +0 -40
  20. package/app/components/GscSourceDebugPanel.vue +0 -195
  21. package/app/components/GscSyncStatusCell.vue +0 -54
  22. package/app/components/GscTopRollupCard.vue +0 -90
  23. package/app/composables/_useGscBackfill.ts +0 -111
  24. package/app/composables/_useGscQueryDispatcher.ts +0 -123
  25. package/app/composables/_useGscResource.ts +0 -114
  26. package/app/composables/_useGscSharedSiteResource.ts +0 -136
  27. package/app/composables/useGscAnalytics.ts +0 -205
  28. package/app/composables/useGscAnalyticsClient.ts +0 -9
  29. package/app/composables/useGscAnalyticsConfig.ts +0 -8
  30. package/app/composables/useGscAnalyticsSourceInfo.ts +0 -159
  31. package/app/composables/useGscAnalyzer.ts +0 -452
  32. package/app/composables/useGscAnalyzerBatch.ts +0 -106
  33. package/app/composables/useGscAnalyzerDefs.ts +0 -118
  34. package/app/composables/useGscAuth.ts +0 -115
  35. package/app/composables/useGscConsoleUrl.ts +0 -45
  36. package/app/composables/useGscCountries.ts +0 -47
  37. package/app/composables/useGscCurrentSite.ts +0 -15
  38. package/app/composables/useGscInspectionHistory.ts +0 -42
  39. package/app/composables/useGscInspections.ts +0 -52
  40. package/app/composables/useGscPanelContext.ts +0 -31
  41. package/app/composables/useGscParquetTable.ts +0 -198
  42. package/app/composables/useGscPeriod.ts +0 -243
  43. package/app/composables/useGscQuery.ts +0 -303
  44. package/app/composables/useGscQueryDispatcher.ts +0 -14
  45. package/app/composables/useGscRequestIndexingInspect.ts +0 -20
  46. package/app/composables/useGscResource.ts +0 -11
  47. package/app/composables/useGscRollup.ts +0 -252
  48. package/app/composables/useGscSearchAppearance.ts +0 -47
  49. package/app/composables/useGscSiteAnalyzer.ts +0 -500
  50. package/app/composables/useGscSitemapHistory.ts +0 -41
  51. package/app/composables/useGscSitemaps.ts +0 -45
  52. package/app/composables/useGscTableState.ts +0 -119
  53. package/app/plugins/analytics.ts +0 -57
  54. package/app/utils/anonymization.ts +0 -24
  55. package/app/utils/country-names.ts +0 -56
  56. package/app/utils/duckdb-wasm.ts +0 -166
  57. package/app/utils/gsc-constants.ts +0 -10
  58. package/app/utils/gsc-error.ts +0 -58
  59. package/app/utils/gsc-fetch.ts +0 -94
  60. package/app/utils/gsc-filters.ts +0 -32
  61. package/app/utils/gsc-rows.ts +0 -72
  62. package/app/utils/position.ts +0 -7
  63. package/module.ts +0 -81
  64. package/nuxt.config.ts +0 -41
  65. package/types.ts +0 -119
@@ -1,243 +0,0 @@
1
- // Period + comparison primitives. Pure utilities (date math, growth calc)
2
- // plus the reactive `useGscPeriod()` composable that wires them into refs.
3
- //
4
- // Supports rolling presets (7d/28d/3m/6m/12m), calendar presets (this-week,
5
- // this-month, last-month, this-quarter, this-year), and custom ranges
6
- // (`custom:start:end` or `custom:start:end:prevStart:prevEnd`).
7
- //
8
- // Timezone via `runtimeConfig.public.analytics.timezone` (IANA name) or
9
- // the `timezone` option. Default: UTC.
10
-
11
- import { GSC_STABLE_LATENCY_DAYS } from '../utils/gsc-constants'
12
-
13
- export type RollingPeriod = '7d' | '28d' | '3m' | '6m' | '12m'
14
- export type CalendarPeriod = 'this-week' | 'this-month' | 'last-month' | 'this-quarter' | 'this-year'
15
- export type CustomPeriod = `custom:${string}:${string}` | `custom:${string}:${string}:${string}:${string}`
16
- export type Period = RollingPeriod | CalendarPeriod | CustomPeriod
17
- export type CompareMode = 'previous' | 'year' | 'none'
18
-
19
- export interface PeriodPreset {
20
- value: RollingPeriod | CalendarPeriod
21
- label: string
22
- shortLabel: string
23
- days: number
24
- }
25
-
26
- export const PERIOD_PRESETS = [
27
- { value: '7d', label: 'Last 7 days', shortLabel: '7d', days: 7 },
28
- { value: '28d', label: 'Last 28 days', shortLabel: '28d', days: 28 },
29
- { value: '3m', label: 'Last 3 months', shortLabel: '3m', days: 90 },
30
- { value: '6m', label: 'Last 6 months', shortLabel: '6m', days: 180 },
31
- { value: '12m', label: 'Last 12 months', shortLabel: '12m', days: 365 },
32
- { value: 'this-week', label: 'This week', shortLabel: 'WTD', days: 7 },
33
- { value: 'this-month', label: 'This month', shortLabel: 'MTD', days: 31 },
34
- { value: 'last-month', label: 'Last month', shortLabel: 'LM', days: 31 },
35
- { value: 'this-quarter', label: 'This quarter', shortLabel: 'QTD', days: 92 },
36
- { value: 'this-year', label: 'This year', shortLabel: 'YTD', days: 365 },
37
- ] as const satisfies readonly PeriodPreset[]
38
-
39
- export const COMPARE_OPTIONS: readonly { value: CompareMode, label: string }[] = [
40
- { value: 'previous', label: 'Previous period' },
41
- { value: 'year', label: 'Year over year' },
42
- { value: 'none', label: 'No comparison' },
43
- ]
44
-
45
- export interface DateRange {
46
- start: string
47
- end: string
48
- days: number
49
- prevStart: string
50
- prevEnd: string
51
- yearStart: string
52
- yearEnd: string
53
- }
54
-
55
- export function isCustomPeriod(p: Period | string): p is CustomPeriod {
56
- return typeof p === 'string' && p.startsWith('custom:')
57
- }
58
-
59
- export function parseCustomPeriod(p: Period | string): { start: string, end: string, prevStart?: string, prevEnd?: string } | null {
60
- if (!isCustomPeriod(p))
61
- return null
62
- const [, start, end, prevStart, prevEnd] = p.split(':')
63
- if (!start || !end)
64
- return null
65
- return prevStart && prevEnd ? { start, end, prevStart, prevEnd } : { start, end }
66
- }
67
-
68
- export interface PeriodOptions {
69
- /** Subtract GSC's stable-data latency from `end`. Default `true`. */
70
- stableData?: boolean
71
- /** IANA timezone (e.g. 'America/Los_Angeles'). Default UTC. */
72
- timezone?: string
73
- }
74
-
75
- export function periodToDateRange(period: Period | string, opts: PeriodOptions = {}): DateRange {
76
- const stableData = opts.stableData ?? true
77
- const custom = parseCustomPeriod(period)
78
- if (custom) {
79
- const range = buildRange(parseIso(custom.start), parseIso(custom.end))
80
- if (custom.prevStart && custom.prevEnd) {
81
- return {
82
- ...range,
83
- prevStart: custom.prevStart,
84
- prevEnd: custom.prevEnd,
85
- yearStart: custom.prevStart,
86
- yearEnd: custom.prevEnd,
87
- }
88
- }
89
- return range
90
- }
91
-
92
- const today = todayInTz(opts.timezone)
93
- const end = stableData ? addDays(today, -GSC_STABLE_LATENCY_DAYS) : addDays(today, -1)
94
-
95
- switch (period) {
96
- case '7d': return buildRange(addDays(end, -6), end)
97
- case '28d': return buildRange(addDays(end, -27), end)
98
- case '3m': return buildRange(addDays(end, -89), end)
99
- case '6m': return buildRange(addDays(end, -179), end)
100
- case '12m': return buildRange(addDays(end, -364), end)
101
- case 'this-week': return buildRange(startOfWeek(end), end)
102
- case 'this-month': return buildRange(startOfMonth(end), end)
103
- case 'last-month': {
104
- const prev = addDays(startOfMonth(end), -1)
105
- return buildRange(startOfMonth(prev), endOfMonth(prev))
106
- }
107
- case 'this-quarter': return buildRange(startOfQuarter(end), end)
108
- case 'this-year': return buildRange(startOfYear(end), end)
109
- default: return buildRange(addDays(end, -27), end)
110
- }
111
- }
112
-
113
- export function periodToDays(period: Period | string, opts?: PeriodOptions): number {
114
- return periodToDateRange(period, opts).days
115
- }
116
-
117
- export function compareRange(range: DateRange, mode: CompareMode): { start: string, end: string } | null {
118
- if (mode === 'none')
119
- return null
120
- if (mode === 'year')
121
- return { start: range.yearStart, end: range.yearEnd }
122
- return { start: range.prevStart, end: range.prevEnd }
123
- }
124
-
125
- export function getPeriodLabel(period: Period | string): string {
126
- if (isCustomPeriod(period)) {
127
- const c = parseCustomPeriod(period)
128
- return c ? `${c.start} → ${c.end}` : 'Custom'
129
- }
130
- return PERIOD_PRESETS.find(p => p.value === period)?.label ?? String(period)
131
- }
132
-
133
- /**
134
- * Relative growth as a fraction (0.12 = +12%). `null` if prev is zero or
135
- * either side missing — caller renders "—" rather than divide-by-zero.
136
- */
137
- export function computeGrowth(current: number, previous: number | null | undefined): number | null {
138
- if (previous == null || previous === 0)
139
- return null
140
- return (current - previous) / previous
141
- }
142
-
143
- export interface UseGscPeriodOptions {
144
- defaultPeriod?: Period
145
- defaultCompareMode?: CompareMode
146
- defaultStableData?: boolean
147
- /** Override `runtimeConfig.public.analytics.timezone`. */
148
- timezone?: string
149
- }
150
-
151
- export interface UseGscPeriodReturn {
152
- period: Ref<Period>
153
- compareMode: Ref<CompareMode>
154
- stableData: Ref<boolean>
155
- range: ComputedRef<DateRange>
156
- comparison: ComputedRef<{ start: string, end: string } | null>
157
- label: ComputedRef<string>
158
- presets: typeof PERIOD_PRESETS
159
- compareOptions: typeof COMPARE_OPTIONS
160
- }
161
-
162
- export function useGscPeriod(opts: UseGscPeriodOptions = {}): UseGscPeriodReturn {
163
- const period = useState<Period>('gsc:period', () => opts.defaultPeriod ?? '28d')
164
- const compareMode = useState<CompareMode>('gsc:compareMode', () => opts.defaultCompareMode ?? 'previous')
165
- const stableData = useState<boolean>('gsc:stableData', () => opts.defaultStableData ?? true)
166
-
167
- const cfg = useRuntimeConfig().public.analytics as { timezone?: string } | undefined
168
- const timezone = opts.timezone ?? cfg?.timezone ?? undefined
169
-
170
- const range = computed(() => periodToDateRange(period.value, { stableData: stableData.value, timezone }))
171
- const comparison = computed(() => compareRange(range.value, compareMode.value))
172
- const label = computed(() => getPeriodLabel(period.value))
173
-
174
- return {
175
- period,
176
- compareMode,
177
- stableData,
178
- range,
179
- comparison,
180
- label,
181
- presets: PERIOD_PRESETS,
182
- compareOptions: COMPARE_OPTIONS,
183
- }
184
- }
185
-
186
- function todayInTz(tz?: string): Date {
187
- if (!tz) {
188
- const now = new Date()
189
- return new Date(`${now.toISOString().slice(0, 10)}T00:00:00Z`)
190
- }
191
- const fmt = new Intl.DateTimeFormat('en-CA', { timeZone: tz, year: 'numeric', month: '2-digit', day: '2-digit' })
192
- return new Date(`${fmt.format(new Date())}T00:00:00Z`)
193
- }
194
-
195
- function parseIso(s: string): Date {
196
- return new Date(`${s}T00:00:00Z`)
197
- }
198
-
199
- function addDays(d: Date, n: number): Date {
200
- const out = new Date(d)
201
- out.setUTCDate(out.getUTCDate() + n)
202
- return out
203
- }
204
-
205
- function iso(d: Date): string {
206
- return d.toISOString().slice(0, 10)
207
- }
208
-
209
- function startOfWeek(d: Date): Date {
210
- const day = d.getUTCDay()
211
- return addDays(d, day === 0 ? -6 : 1 - day)
212
- }
213
-
214
- function startOfMonth(d: Date): Date {
215
- return new Date(Date.UTC(d.getUTCFullYear(), d.getUTCMonth(), 1))
216
- }
217
-
218
- function endOfMonth(d: Date): Date {
219
- return new Date(Date.UTC(d.getUTCFullYear(), d.getUTCMonth() + 1, 0))
220
- }
221
-
222
- function startOfQuarter(d: Date): Date {
223
- return new Date(Date.UTC(d.getUTCFullYear(), Math.floor(d.getUTCMonth() / 3) * 3, 1))
224
- }
225
-
226
- function startOfYear(d: Date): Date {
227
- return new Date(Date.UTC(d.getUTCFullYear(), 0, 1))
228
- }
229
-
230
- function buildRange(start: Date, end: Date): DateRange {
231
- const days = Math.round((end.getTime() - start.getTime()) / 86400000) + 1
232
- const prevEnd = addDays(start, -1)
233
- const prevStart = addDays(prevEnd, -(days - 1))
234
- return {
235
- start: iso(start),
236
- end: iso(end),
237
- days,
238
- prevStart: iso(prevStart),
239
- prevEnd: iso(prevEnd),
240
- yearStart: iso(addDays(start, -365)),
241
- yearEnd: iso(addDays(end, -365)),
242
- }
243
- }
@@ -1,303 +0,0 @@
1
- // Unified analytics query hook. Dispatches browser DuckDB-WASM vs server
2
- // fallback, abort-safe, with typed status enum and opt-in backfill-on-demand.
3
- //
4
- // `site` is a parameter (not global state) so multi-site pages can issue
5
- // independent queries. `serverFallback` is optional — the default POSTs
6
- // AnalysisParams to `/api/__gsc/sites/[siteId]/analyze`. Override if your server
7
- // uses a different contract.
8
-
9
- import type { AnalysisParams, AnalysisResult } from '@gscdump/analysis'
10
- import type { ComputedRef, Ref, WatchSource } from '@vue/runtime-core'
11
- import { classifyGscError } from '../utils/gsc-error'
12
- import { useGscBackfill } from './_useGscBackfill'
13
- import { useGscQueryDispatcher } from './_useGscQueryDispatcher'
14
- import { useGscAnalyticsClient } from './useGscAnalyticsClient'
15
- import { useGscAnalyzer } from './useGscAnalyzer'
16
- import { _useGscAuthInternal } from './useGscAuth'
17
-
18
- export type GscQueryEngine = 'auto' | 'browser' | 'server'
19
-
20
- export type GscQueryStatus
21
- = | 'idle'
22
- | 'pending'
23
- | 'success'
24
- | 'empty'
25
- | 'error'
26
- | 'auth-missing'
27
- | 'rate-limited'
28
- | 'network'
29
-
30
- /**
31
- * Why a given query ended up on `browser` or `server`. Surfaced for
32
- * dev tooling so 0% R2 utilisation can be diagnosed without guessing
33
- * (e.g. opt-in off vs. site not eligible vs. attach failure).
34
- */
35
- export type GscQueryDecisionReason
36
- = | 'idle'
37
- | 'ssr'
38
- | 'disabled'
39
- | 'forced:server'
40
- | 'forced:browser'
41
- | 'optin:off'
42
- | 'auto:browser'
43
- | 'auto:fallback'
44
-
45
- export interface GscQueryDecision {
46
- mode: 'browser' | 'server' | null
47
- reason: GscQueryDecisionReason
48
- /** Free-text detail for `auto:fallback` (the underlying error message). */
49
- detail?: string
50
- }
51
-
52
- export interface GscQueryMeta {
53
- raw: Record<string, unknown> | null
54
- backfillRequired?: { startDate: string, endDate: string }
55
- retryAfter?: number
56
- }
57
-
58
- export type GscBackfillRunner = ReturnType<typeof useGscBackfill>
59
-
60
- export interface UseGscQueryOptions<T> {
61
- /** Site id (reactive). Required — queries don't dispatch without a site. */
62
- site: MaybeRefOrGetter<string | null | undefined>
63
- /** Reactive analysis params (discriminated union by `.type`). */
64
- params: MaybeRefOrGetter<AnalysisParams>
65
- /**
66
- * Transform raw `AnalysisResult` from the browser path into the consumer type.
67
- * Omit to pass through as-is (return type becomes `AnalysisResult`).
68
- */
69
- reshape?: (raw: AnalysisResult) => T
70
- /**
71
- * Server fallback override. Defaults to POSTing the params to
72
- * `/api/__gsc/sites/[siteId]/analyze` and returning the response body.
73
- */
74
- serverFallback?: (siteId: string, params: AnalysisParams) => Promise<T>
75
- /**
76
- * Force a specific engine. Default `'auto'`. Accepts a getter so callers
77
- * can swing engine reactively (e.g. flip to `'server'` when the requested
78
- * range overlaps a known coverage gap). Re-read on every `runQuery` and
79
- * also included in the watcher graph so changes trigger a refetch.
80
- */
81
- engine?: MaybeRefOrGetter<GscQueryEngine>
82
- /** Extra reactive sources that should trigger refetch. */
83
- watchSources?: WatchSource[]
84
- /** Extract meta from the consumer payload. Defaults to `(out as any).meta`. */
85
- extractMeta?: (out: T) => Record<string, unknown> | null | undefined
86
- /**
87
- * Backfill handling. `false` (default) = off. `true` = spin up a dedicated
88
- * `useGscBackfill()`. Pass an existing runner to share across queries.
89
- */
90
- backfill?: boolean | GscBackfillRunner
91
- /** Gate the query — when `false` it stays idle. */
92
- enabled?: MaybeRefOrGetter<boolean>
93
- }
94
-
95
- export interface UseGscQueryReturn<T> {
96
- data: Ref<T | null>
97
- status: Ref<GscQueryStatus>
98
- pending: ComputedRef<boolean>
99
- error: Ref<Error | null>
100
- engine: Ref<'browser' | 'server' | null>
101
- elapsedMs: Ref<number | null>
102
- fallbackReason: Ref<string | null>
103
- /** Why this query ran where it ran. Updated on every dispatch. */
104
- lastDecision: Ref<GscQueryDecision>
105
- meta: Ref<GscQueryMeta>
106
- backfill: GscBackfillRunner | null
107
- refresh: () => Promise<void>
108
- }
109
-
110
- function classifyError(e: unknown): { status: GscQueryStatus, retryAfter?: number } {
111
- const c = classifyGscError(e)
112
- return { status: c.status as GscQueryStatus, retryAfter: c.retryAfter }
113
- }
114
-
115
- function isEmpty(v: unknown): boolean {
116
- if (v == null)
117
- return true
118
- if (Array.isArray(v))
119
- return v.length === 0
120
- if (typeof v === 'object') {
121
- const rec = v as Record<string, unknown>
122
- for (const key of ['rows', 'results', 'daily', 'items']) {
123
- const arr = rec[key]
124
- if (Array.isArray(arr))
125
- return arr.length === 0
126
- }
127
- }
128
- return false
129
- }
130
-
131
- async function defaultServerFallback<T>(siteId: string, params: AnalysisParams): Promise<T> {
132
- return await useGscAnalyticsClient().analyze<T>(siteId, params)
133
- }
134
-
135
- const DEFAULT_SEARCH_TYPE: NonNullable<AnalysisParams['searchType']> = 'web'
136
-
137
- function withDefaultSearchType(params: AnalysisParams): AnalysisParams {
138
- return { ...params, searchType: params.searchType ?? DEFAULT_SEARCH_TYPE }
139
- }
140
-
141
- export function useGscQuery<T = AnalysisResult>(opts: UseGscQueryOptions<T>): UseGscQueryReturn<T> {
142
- const querySearchType = computed(() => toValue(opts.params).searchType ?? DEFAULT_SEARCH_TYPE)
143
- const queryRange = computed(() => {
144
- const params = toValue(opts.params)
145
- return typeof params.startDate === 'string' && typeof params.endDate === 'string'
146
- ? { start: params.startDate, end: params.endDate }
147
- : null
148
- })
149
- const analyzer = useGscAnalyzer(opts.site, querySearchType, queryRange)
150
- const dispatcher = useGscQueryDispatcher()
151
-
152
- const data = shallowRef<T | null>(null)
153
- const status = ref<GscQueryStatus>('idle')
154
- const error = ref<Error | null>(null)
155
- const engine = ref<'browser' | 'server' | null>(null)
156
- const elapsedMs = ref<number | null>(null)
157
- const fallbackReason = ref<string | null>(null)
158
- const lastDecision = ref<GscQueryDecision>({ mode: null, reason: 'idle' })
159
- const meta = ref<GscQueryMeta>({ raw: null })
160
- const pending = computed(() => status.value === 'pending')
161
-
162
- function extractMeta(out: T): Record<string, unknown> | null {
163
- if (opts.extractMeta) {
164
- const m = opts.extractMeta(out)
165
- return m ?? null
166
- }
167
- const m = (out as unknown as { meta?: Record<string, unknown> })?.meta
168
- return m ?? null
169
- }
170
-
171
- function captureMeta(out: T): void {
172
- const raw = extractMeta(out)
173
- const backfillRequired = (raw as { backfillRequired?: { startDate: string, endDate: string } } | null)?.backfillRequired
174
- meta.value = backfillRequired ? { raw, backfillRequired } : { raw }
175
- }
176
-
177
- async function runServer(siteId: string): Promise<void> {
178
- const t0 = performance.now()
179
- const fn = opts.serverFallback ?? defaultServerFallback
180
- const out = await fn(siteId, withDefaultSearchType(toValue(opts.params))) as T
181
- data.value = out
182
- engine.value = 'server'
183
- elapsedMs.value = performance.now() - t0
184
- captureMeta(out)
185
- }
186
-
187
- async function runBrowser(signal: AbortSignal): Promise<void> {
188
- const out = await analyzer.analyze(withDefaultSearchType(toValue(opts.params)), { signal })
189
- signal.throwIfAborted()
190
- const shaped = opts.reshape ? opts.reshape(out) : (out as unknown as T)
191
- data.value = shaped
192
- engine.value = 'browser'
193
- elapsedMs.value = (out as unknown as { queryMs?: number }).queryMs ?? null
194
- captureMeta(shaped)
195
- }
196
-
197
- let activeController: AbortController | null = null
198
-
199
- async function runQuery(): Promise<void> {
200
- if (!import.meta.client) {
201
- status.value = 'idle'
202
- lastDecision.value = { mode: null, reason: 'ssr' }
203
- return
204
- }
205
- if (opts.enabled && !toValue(opts.enabled)) {
206
- status.value = 'idle'
207
- data.value = null
208
- engine.value = null
209
- elapsedMs.value = null
210
- lastDecision.value = { mode: null, reason: 'disabled' }
211
- return
212
- }
213
- const siteId = toValue(opts.site)
214
- if (!siteId) {
215
- status.value = 'idle'
216
- lastDecision.value = { mode: null, reason: 'idle' }
217
- return
218
- }
219
- activeController?.abort()
220
- const controller = new AbortController()
221
- activeController = controller
222
- status.value = 'pending'
223
- error.value = null
224
- fallbackReason.value = null
225
-
226
- const decision = dispatcher.pickEngine(_useGscAuthInternal().value, { perCall: toValue(opts.engine) })
227
- lastDecision.value = decision
228
-
229
- try {
230
- if (decision.mode === 'server') {
231
- await runServer(siteId)
232
- }
233
- else {
234
- // Browser-mode: when the caller asked for `auto` we fall back to server
235
- // on failure; explicit `browser` propagates the error.
236
- await runBrowser(controller.signal).catch(async (e) => {
237
- if (e?.name === 'AbortError')
238
- throw e
239
- if (decision.requested !== 'auto')
240
- throw e
241
- fallbackReason.value = e instanceof Error ? e.message : String(e)
242
- console.warn('[useGscQuery] browser failed, falling back to server:', fallbackReason.value)
243
- dispatcher.reportFallback({
244
- reason: fallbackReason.value,
245
- at: Date.now(),
246
- url: typeof location !== 'undefined' ? location.pathname : '',
247
- })
248
- lastDecision.value = { mode: 'server', reason: 'auto:fallback', detail: fallbackReason.value }
249
- await runServer(siteId)
250
- })
251
- }
252
- if (!controller.signal.aborted)
253
- status.value = isEmpty(data.value) ? 'empty' : 'success'
254
- }
255
- catch (e) {
256
- if ((e as { name?: string })?.name === 'AbortError')
257
- return
258
- error.value = e instanceof Error ? e : new Error(String(e))
259
- const classified = classifyError(e)
260
- status.value = classified.status
261
- if (classified.retryAfter != null)
262
- meta.value = { ...meta.value, retryAfter: classified.retryAfter }
263
- }
264
- finally {
265
- if (activeController === controller)
266
- activeController = null
267
- }
268
- }
269
-
270
- if (import.meta.client) {
271
- onScopeDispose(() => {
272
- activeController?.abort()
273
- activeController = null
274
- })
275
- }
276
-
277
- const sources: WatchSource[] = [
278
- () => toValue(opts.site),
279
- () => toValue(opts.params),
280
- ...(opts.watchSources ?? []),
281
- ]
282
- if (opts.enabled)
283
- sources.push(() => toValue(opts.enabled))
284
- if (opts.engine !== undefined)
285
- sources.push(() => toValue(opts.engine))
286
- watch(sources, runQuery, { deep: true, immediate: true })
287
-
288
- let backfill: GscBackfillRunner | null = null
289
- const b = opts.backfill ?? false
290
- if (b !== false) {
291
- backfill = b === true ? useGscBackfill() : b
292
- watch(() => meta.value.backfillRequired, (req: { startDate: string, endDate: string } | undefined) => {
293
- if (!req)
294
- return
295
- const siteId = toValue(opts.site)
296
- if (!siteId)
297
- return
298
- backfill!.maybeTrigger({ backfillRequired: req }, siteId, runQuery)
299
- }, { immediate: true })
300
- }
301
-
302
- return { data, status, pending, error, engine, elapsedMs, fallbackReason, lastDecision, meta, backfill, refresh: runQuery }
303
- }
@@ -1,14 +0,0 @@
1
- // Public re-export. The implementation lives in `_useGscQueryDispatcher.ts`;
2
- // the module's `imports:extend` hook strips `_use*` from host auto-imports so
3
- // consumers see the wrapper at `useGscQueryDispatcher` and the layer's own
4
- // composables import the internal file directly. Named re-exports because
5
- // Nuxt's auto-import scanner doesn't traverse `export *`.
6
- export {
7
- type CreateDefaultDispatcherOpts,
8
- createDefaultGscQueryDispatcher,
9
- type GscEngineDecision,
10
- type GscFallbackEvent,
11
- type GscQueryDispatcher,
12
- type PickEngineOpts,
13
- useGscQueryDispatcher,
14
- } from './_useGscQueryDispatcher'
@@ -1,20 +0,0 @@
1
- import type { IndexingInspectRateLimited, IndexingInspectResponse } from '@gscdump/contracts'
2
- import { useGscAnalyticsClient } from './useGscAnalyticsClient'
3
-
4
- export function useGscRequestIndexingInspect(): (
5
- siteId: string,
6
- urls: string[],
7
- ) => Promise<IndexingInspectResponse | IndexingInspectRateLimited> {
8
- const client = useGscAnalyticsClient()
9
- return async (siteId, urls) => {
10
- return client.requestIndexingInspect(siteId, { urls }).catch((err: unknown) => {
11
- const status = (err as { status?: number, statusCode?: number, response?: { status?: number } })?.status
12
- ?? (err as { statusCode?: number })?.statusCode
13
- ?? (err as { response?: { status?: number } })?.response?.status
14
- const data = (err as { data?: unknown })?.data as IndexingInspectRateLimited | undefined
15
- if (status === 429 && data && data.error === 'rate_limited')
16
- return data
17
- throw err
18
- })
19
- }
20
- }
@@ -1,11 +0,0 @@
1
- // Public re-export. The implementation lives in `_useGscResource.ts`; the
2
- // module's `imports:extend` hook strips `_use*` from host auto-imports so
3
- // consumers see the wrapper at `useGscResource` and the layer's own
4
- // composables import the internal file directly. Named re-exports because
5
- // Nuxt's auto-import scanner doesn't traverse `export *`.
6
- export {
7
- type GscResourceStatus,
8
- useGscResource,
9
- type UseGscResourceOptions,
10
- type UseGscResourceReturn,
11
- } from './_useGscResource'