@gscdump/nuxt 0.22.4 → 1.5.3

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 (66) hide show
  1. package/README.md +10 -137
  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 +26 -176
  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 -214
  15. package/app/components/GscLazyTopResult.vue +0 -47
  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 -196
  21. package/app/components/GscSyncStatusCell.vue +0 -54
  22. package/app/components/GscTopRollupCard.vue +0 -90
  23. package/app/composables/_useGscBackfill.ts +0 -119
  24. package/app/composables/_useGscQueryDispatcher.ts +0 -123
  25. package/app/composables/_useGscResource.ts +0 -202
  26. package/app/composables/_useGscSharedSiteResource.ts +0 -136
  27. package/app/composables/useGscAnalytics.ts +0 -206
  28. package/app/composables/useGscAnalyticsClient.ts +0 -9
  29. package/app/composables/useGscAnalyticsConfig.ts +0 -8
  30. package/app/composables/useGscAnalyticsSourceInfo.ts +0 -165
  31. package/app/composables/useGscAnalyzer.ts +0 -460
  32. package/app/composables/useGscAnalyzerBatch.ts +0 -106
  33. package/app/composables/useGscAnalyzerDefs.ts +0 -119
  34. package/app/composables/useGscAuth.ts +0 -115
  35. package/app/composables/useGscConsoleUrl.ts +0 -45
  36. package/app/composables/useGscCountries.ts +0 -46
  37. package/app/composables/useGscCurrentSite.ts +0 -36
  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/useGscPeriod.ts +0 -243
  42. package/app/composables/useGscQuery.ts +0 -301
  43. package/app/composables/useGscQueryDispatcher.ts +0 -14
  44. package/app/composables/useGscRequestIndexingInspect.ts +0 -29
  45. package/app/composables/useGscResource.ts +0 -11
  46. package/app/composables/useGscRollup.ts +0 -237
  47. package/app/composables/useGscSearchAppearance.ts +0 -46
  48. package/app/composables/useGscSiteAnalyzer.ts +0 -456
  49. package/app/composables/useGscSitemapHistory.ts +0 -41
  50. package/app/composables/useGscSitemaps.ts +0 -45
  51. package/app/composables/useGscTableState.ts +0 -119
  52. package/app/plugins/analytics.ts +0 -57
  53. package/app/queries/gsc.ts +0 -163
  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/gsc-rpc.ts +0 -18
  63. package/app/utils/position.ts +0 -7
  64. package/module.ts +0 -81
  65. package/nuxt.config.ts +0 -61
  66. package/types.ts +0 -115
@@ -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,301 +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/engine/analysis-types'
10
- import type { ComputedRef, Ref, WatchSource } from '@vue/runtime-core'
11
- import { gscQueries, withDefaultGscSearchType } from '../queries/gsc'
12
- import { classifyGscError } from '../utils/gsc-error'
13
- import { useGscRpc } from '../utils/gsc-rpc'
14
- import { useGscBackfill } from './_useGscBackfill'
15
- import { useGscQueryDispatcher } from './_useGscQueryDispatcher'
16
- import { useGscAnalyzer } from './useGscAnalyzer'
17
- import { _useGscAuthInternal } from './useGscAuth'
18
-
19
- export type GscQueryEngine = 'auto' | 'browser' | 'server'
20
-
21
- export type GscQueryStatus
22
- = | 'idle'
23
- | 'pending'
24
- | 'success'
25
- | 'empty'
26
- | 'error'
27
- | 'auth-missing'
28
- | 'rate-limited'
29
- | 'network'
30
-
31
- /**
32
- * Why a given query ended up on `browser` or `server`. Surfaced for
33
- * dev tooling so 0% R2 utilisation can be diagnosed without guessing
34
- * (e.g. opt-in off vs. site not eligible vs. attach failure).
35
- */
36
- export type GscQueryDecisionReason
37
- = | 'idle'
38
- | 'ssr'
39
- | 'disabled'
40
- | 'forced:server'
41
- | 'forced:browser'
42
- | 'optin:off'
43
- | 'auto:browser'
44
- | 'auto:fallback'
45
-
46
- export interface GscQueryDecision {
47
- mode: 'browser' | 'server' | null
48
- reason: GscQueryDecisionReason
49
- /** Free-text detail for `auto:fallback` (the underlying error message). */
50
- detail?: string
51
- }
52
-
53
- export interface GscQueryMeta {
54
- raw: Record<string, unknown> | null
55
- backfillRequired?: { startDate: string, endDate: string }
56
- retryAfter?: number
57
- }
58
-
59
- export type GscBackfillRunner = ReturnType<typeof useGscBackfill>
60
-
61
- export interface UseGscQueryOptions<T> {
62
- /** Site id (reactive). Required — queries don't dispatch without a site. */
63
- site: MaybeRefOrGetter<string | null | undefined>
64
- /** Reactive analysis params (discriminated union by `.type`). */
65
- params: MaybeRefOrGetter<AnalysisParams>
66
- /**
67
- * Transform raw `AnalysisResult` from the browser path into the consumer type.
68
- * Omit to pass through as-is (return type becomes `AnalysisResult`).
69
- */
70
- reshape?: (raw: AnalysisResult) => T
71
- /**
72
- * Server fallback override. Defaults to POSTing the params to
73
- * `/api/__gsc/sites/[siteId]/analyze` and returning the response body.
74
- */
75
- serverFallback?: (siteId: string, params: AnalysisParams) => Promise<T>
76
- /**
77
- * Force a specific engine. Default `'auto'`. Accepts a getter so callers
78
- * can swing engine reactively (e.g. flip to `'server'` when the requested
79
- * range overlaps a known coverage gap). Re-read on every `runQuery` and
80
- * also included in the watcher graph so changes trigger a refetch.
81
- */
82
- engine?: MaybeRefOrGetter<GscQueryEngine>
83
- /** Extra reactive sources that should trigger refetch. */
84
- watchSources?: WatchSource[]
85
- /** Extract meta from the consumer payload. Defaults to `(out as any).meta`. */
86
- extractMeta?: (out: T) => Record<string, unknown> | null | undefined
87
- /**
88
- * Backfill handling. `false` (default) = off. `true` = spin up a dedicated
89
- * `useGscBackfill()`. Pass an existing runner to share across queries.
90
- */
91
- backfill?: boolean | GscBackfillRunner
92
- /** Gate the query — when `false` it stays idle. */
93
- enabled?: MaybeRefOrGetter<boolean>
94
- }
95
-
96
- export interface UseGscQueryReturn<T> {
97
- data: Ref<T | null>
98
- status: Ref<GscQueryStatus>
99
- pending: ComputedRef<boolean>
100
- error: Ref<Error | null>
101
- engine: Ref<'browser' | 'server' | null>
102
- elapsedMs: Ref<number | null>
103
- fallbackReason: Ref<string | null>
104
- /** Why this query ran where it ran. Updated on every dispatch. */
105
- lastDecision: Ref<GscQueryDecision>
106
- meta: Ref<GscQueryMeta>
107
- backfill: GscBackfillRunner | null
108
- refresh: () => Promise<void>
109
- }
110
-
111
- function classifyError(e: unknown): { status: GscQueryStatus, retryAfter?: number } {
112
- const c = classifyGscError(e)
113
- return { status: c.status as GscQueryStatus, retryAfter: c.retryAfter }
114
- }
115
-
116
- function isEmpty(v: unknown): boolean {
117
- if (v == null)
118
- return true
119
- if (Array.isArray(v))
120
- return v.length === 0
121
- if (typeof v === 'object') {
122
- const rec = v as Record<string, unknown>
123
- for (const key of ['rows', 'results', 'daily', 'items']) {
124
- const arr = rec[key]
125
- if (Array.isArray(arr))
126
- return arr.length === 0
127
- }
128
- }
129
- return false
130
- }
131
-
132
- const DEFAULT_SEARCH_TYPE: NonNullable<AnalysisParams['searchType']> = 'web'
133
-
134
- function withDefaultSearchType(params: AnalysisParams): AnalysisParams {
135
- return withDefaultGscSearchType(params)
136
- }
137
-
138
- export function useGscQuery<T = AnalysisResult>(opts: UseGscQueryOptions<T>): UseGscQueryReturn<T> {
139
- const rpc = useGscRpc()
140
- const querySearchType = computed(() => toValue(opts.params).searchType ?? DEFAULT_SEARCH_TYPE)
141
- const queryRange = computed(() => {
142
- const params = toValue(opts.params)
143
- return typeof params.startDate === 'string' && typeof params.endDate === 'string'
144
- ? { start: params.startDate, end: params.endDate }
145
- : null
146
- })
147
- const analyzer = useGscAnalyzer(opts.site, querySearchType, queryRange)
148
- const dispatcher = useGscQueryDispatcher()
149
-
150
- const data = shallowRef<T | null>(null)
151
- const status = ref<GscQueryStatus>('idle')
152
- const error = ref<Error | null>(null)
153
- const engine = ref<'browser' | 'server' | null>(null)
154
- const elapsedMs = ref<number | null>(null)
155
- const fallbackReason = ref<string | null>(null)
156
- const lastDecision = ref<GscQueryDecision>({ mode: null, reason: 'idle' })
157
- const meta = ref<GscQueryMeta>({ raw: null })
158
- const pending = computed(() => status.value === 'pending')
159
-
160
- function extractMeta(out: T): Record<string, unknown> | null {
161
- if (opts.extractMeta) {
162
- const m = opts.extractMeta(out)
163
- return m ?? null
164
- }
165
- const m = (out as unknown as { meta?: Record<string, unknown> })?.meta
166
- return m ?? null
167
- }
168
-
169
- function captureMeta(out: T): void {
170
- const raw = extractMeta(out)
171
- const backfillRequired = (raw as { backfillRequired?: { startDate: string, endDate: string } } | null)?.backfillRequired
172
- meta.value = backfillRequired ? { raw, backfillRequired } : { raw }
173
- }
174
-
175
- async function runServer(siteId: string): Promise<void> {
176
- const t0 = performance.now()
177
- const fn = opts.serverFallback ?? (async (siteId: string, params: AnalysisParams) => rpc.execute(gscQueries.analyze(siteId), params as never, { silent: true }) as Promise<T>)
178
- const out = await fn(siteId, withDefaultSearchType(toValue(opts.params))) as T
179
- data.value = out
180
- engine.value = 'server'
181
- elapsedMs.value = performance.now() - t0
182
- captureMeta(out)
183
- }
184
-
185
- async function runBrowser(signal: AbortSignal): Promise<void> {
186
- const out = await analyzer.analyze(withDefaultSearchType(toValue(opts.params)), { signal })
187
- signal.throwIfAborted()
188
- const shaped = opts.reshape ? opts.reshape(out) : (out as unknown as T)
189
- data.value = shaped
190
- engine.value = 'browser'
191
- elapsedMs.value = (out as unknown as { queryMs?: number }).queryMs ?? null
192
- captureMeta(shaped)
193
- }
194
-
195
- let activeController: AbortController | null = null
196
-
197
- async function runQuery(): Promise<void> {
198
- if (!import.meta.client) {
199
- status.value = 'idle'
200
- lastDecision.value = { mode: null, reason: 'ssr' }
201
- return
202
- }
203
- if (opts.enabled && !toValue(opts.enabled)) {
204
- status.value = 'idle'
205
- data.value = null
206
- engine.value = null
207
- elapsedMs.value = null
208
- lastDecision.value = { mode: null, reason: 'disabled' }
209
- return
210
- }
211
- const siteId = toValue(opts.site)
212
- if (!siteId) {
213
- status.value = 'idle'
214
- lastDecision.value = { mode: null, reason: 'idle' }
215
- return
216
- }
217
- activeController?.abort()
218
- const controller = new AbortController()
219
- activeController = controller
220
- status.value = 'pending'
221
- error.value = null
222
- fallbackReason.value = null
223
-
224
- const decision = dispatcher.pickEngine(_useGscAuthInternal().value, { perCall: toValue(opts.engine) })
225
- lastDecision.value = decision
226
-
227
- try {
228
- if (decision.mode === 'server') {
229
- await runServer(siteId)
230
- }
231
- else {
232
- // Browser-mode: when the caller asked for `auto` we fall back to server
233
- // on failure; explicit `browser` propagates the error.
234
- await runBrowser(controller.signal).catch(async (e) => {
235
- if (e?.name === 'AbortError')
236
- throw e
237
- if (decision.requested !== 'auto')
238
- throw e
239
- fallbackReason.value = e instanceof Error ? e.message : String(e)
240
- console.warn('[useGscQuery] browser failed, falling back to server:', fallbackReason.value)
241
- dispatcher.reportFallback({
242
- reason: fallbackReason.value,
243
- at: Date.now(),
244
- url: typeof location !== 'undefined' ? location.pathname : '',
245
- })
246
- lastDecision.value = { mode: 'server', reason: 'auto:fallback', detail: fallbackReason.value }
247
- await runServer(siteId)
248
- })
249
- }
250
- if (!controller.signal.aborted)
251
- status.value = isEmpty(data.value) ? 'empty' : 'success'
252
- }
253
- catch (e) {
254
- if ((e as { name?: string })?.name === 'AbortError')
255
- return
256
- error.value = e instanceof Error ? e : new Error(String(e))
257
- const classified = classifyError(e)
258
- status.value = classified.status
259
- if (classified.retryAfter != null)
260
- meta.value = { ...meta.value, retryAfter: classified.retryAfter }
261
- }
262
- finally {
263
- if (activeController === controller)
264
- activeController = null
265
- }
266
- }
267
-
268
- if (import.meta.client) {
269
- onScopeDispose(() => {
270
- activeController?.abort()
271
- activeController = null
272
- })
273
- }
274
-
275
- const sources: WatchSource[] = [
276
- () => toValue(opts.site),
277
- () => toValue(opts.params),
278
- ...(opts.watchSources ?? []),
279
- ]
280
- if (opts.enabled)
281
- sources.push(() => toValue(opts.enabled))
282
- if (opts.engine !== undefined)
283
- sources.push(() => toValue(opts.engine))
284
- watch(sources, runQuery, { deep: true, immediate: true })
285
-
286
- let backfill: GscBackfillRunner | null = null
287
- const b = opts.backfill ?? false
288
- if (b !== false) {
289
- backfill = b === true ? useGscBackfill() : b
290
- watch(() => meta.value.backfillRequired, (req: { startDate: string, endDate: string } | undefined) => {
291
- if (!req)
292
- return
293
- const siteId = toValue(opts.site)
294
- if (!siteId)
295
- return
296
- backfill!.maybeTrigger({ backfillRequired: req }, siteId, runQuery)
297
- }, { immediate: true })
298
- }
299
-
300
- return { data, status, pending, error, engine, elapsedMs, fallbackReason, lastDecision, meta, backfill, refresh: runQuery }
301
- }
@@ -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,29 +0,0 @@
1
- import type { IndexingInspectRateLimited, IndexingInspectResponse } from '@gscdump/contracts'
2
- import { invalidateNuxtQueries } from 'nuxt-use-query/query-cache'
3
- import { gscQueries } from '../queries/gsc'
4
- import { useGscRpc } from '../utils/gsc-rpc'
5
-
6
- export function useGscRequestIndexingInspect(): (
7
- siteId: string,
8
- urls: string[],
9
- ) => Promise<IndexingInspectResponse | IndexingInspectRateLimited> {
10
- const rpc = useGscRpc()
11
- return async (siteId, urls) => {
12
- return rpc.execute(gscQueries.indexingInspect(siteId), { urls }, { silent: true })
13
- .then((res) => {
14
- invalidateNuxtQueries(`gsc:inspections:${siteId}`)
15
- invalidateNuxtQueries(`gsc:inspection-history:${siteId}`)
16
- return res as IndexingInspectResponse | IndexingInspectRateLimited
17
- })
18
- .catch((err: unknown) => {
19
- const e = err as { type?: string, status?: number, statusCode?: number, response?: { status?: number }, data?: unknown }
20
- const status = e.status
21
- ?? e.statusCode
22
- ?? e.response?.status
23
- const data = e.data as IndexingInspectRateLimited | undefined
24
- if (status === 429 && data && data.error === 'rate_limited')
25
- return data
26
- throw err
27
- })
28
- }
29
- }
@@ -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'