@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,136 +0,0 @@
1
- // Per-site shared resource bag. Consolidates the bind/unbind/refcount pattern
2
- // that useGscAnalyzer and useGscAnalyticsSourceInfo each reinvented.
3
- //
4
- // Two entry points share one underlying bag (a module-scope WeakMap keyed by
5
- // the NuxtApp instance, so each app gets isolated state and SSR is safe):
6
- // - useGscSharedSiteResource → composable, refcounted, scope-disposed.
7
- // - acquireSharedEntry → non-composable acquire for boot-time / non-
8
- // setup callers (e.g. analyzer createInstance).
9
- //
10
- // Refcounting via `onDispose` is opt-in:
11
- // - omitted → entry persists forever (cheap, share-only; e.g. /source-info)
12
- // - provided → refcounted; runs onDispose + drops the entry at refs=0
13
- //
14
- // Returns a `bound` computed that tracks the entry for the current siteId. The
15
- // caller wraps it in `computed(() => bound.value?.field ?? default)` to expose
16
- // reactive fields.
17
-
18
- import type { ComputedRef } from '@vue/runtime-core'
19
- import type { NuxtApp } from 'nuxt/app'
20
-
21
- interface CacheEntry<T> {
22
- entry: T
23
- refs: number
24
- }
25
-
26
- type NamespaceBag = Map<string, Map<string, CacheEntry<unknown>>>
27
-
28
- const bags = new WeakMap<NuxtApp, NamespaceBag>()
29
-
30
- function namespaceMap(namespace: string): Map<string, CacheEntry<unknown>> {
31
- const app = useNuxtApp()
32
- let bag = bags.get(app)
33
- if (!bag) {
34
- bag = new Map()
35
- bags.set(app, bag)
36
- }
37
- let m = bag.get(namespace)
38
- if (!m) {
39
- m = new Map()
40
- bag.set(namespace, m)
41
- }
42
- return m
43
- }
44
-
45
- /**
46
- * Non-composable acquire — returns (and lazily creates) the shared entry for
47
- * `(namespace, siteId)`. No refcount, no scope dispose. Intended for callers
48
- * outside a Vue setup scope (analyzer boot IIFE, one-shot loaders) that still
49
- * need to share state with the reactive `useGscSharedSiteResource` consumers.
50
- */
51
- export function acquireSharedEntry<T>(
52
- namespace: string,
53
- siteId: string,
54
- factory: (siteId: string) => T,
55
- ): T {
56
- const cache = namespaceMap(namespace) as Map<string, CacheEntry<T>>
57
- let inst = cache.get(siteId)
58
- if (!inst) {
59
- inst = { entry: factory(siteId), refs: 0 }
60
- cache.set(siteId, inst)
61
- }
62
- return inst.entry
63
- }
64
-
65
- export interface UseSharedSiteResourceOptions<T> {
66
- /** Build the entry on first acquire for a site. */
67
- factory: (siteId: string) => T
68
- /** When set, refcount the entry and run on the last release (deletes from cache). */
69
- onDispose?: (entry: T) => Promise<void> | void
70
- }
71
-
72
- export interface UseSharedSiteResourceReturn<T> {
73
- bound: ComputedRef<T | null>
74
- currentSiteId: Ref<string | null>
75
- }
76
-
77
- export function useGscSharedSiteResource<T>(
78
- namespace: string,
79
- siteId: MaybeRefOrGetter<string | null | undefined>,
80
- opts: UseSharedSiteResourceOptions<T>,
81
- ): UseSharedSiteResourceReturn<T> {
82
- const cache = namespaceMap(namespace) as Map<string, CacheEntry<T>>
83
-
84
- const boundRef = shallowRef<T | null>(null)
85
- const currentSiteId = ref<string | null>(null)
86
-
87
- function bind(id: string): void {
88
- unbind()
89
- let inst = cache.get(id)
90
- if (!inst) {
91
- inst = { entry: opts.factory(id), refs: 0 }
92
- cache.set(id, inst)
93
- }
94
- inst.refs++
95
- boundRef.value = inst.entry
96
- currentSiteId.value = id
97
- }
98
-
99
- function unbind(): void {
100
- const id = currentSiteId.value
101
- if (!id) {
102
- boundRef.value = null
103
- return
104
- }
105
- const inst = cache.get(id)
106
- boundRef.value = null
107
- currentSiteId.value = null
108
- if (!inst)
109
- return
110
- inst.refs--
111
- if (inst.refs <= 0 && opts.onDispose) {
112
- cache.delete(id)
113
- void Promise.resolve(opts.onDispose(inst.entry)).catch(() => {})
114
- }
115
- }
116
-
117
- watch(
118
- () => toValue(siteId),
119
- (id: string | null | undefined) => {
120
- if (!id) {
121
- unbind()
122
- return
123
- }
124
- if (import.meta.client)
125
- bind(id)
126
- },
127
- { immediate: true },
128
- )
129
-
130
- onScopeDispose(unbind)
131
-
132
- return {
133
- bound: computed(() => boundRef.value) as ComputedRef<T | null>,
134
- currentSiteId,
135
- }
136
- }
@@ -1,205 +0,0 @@
1
- // Analytics context: sites list + shared boot-progress map + per-site analyzer
2
- // cache. Provided app-wide by the layer's plugin as `nuxtApp.$gscAnalytics`.
3
- // Pages reach in via narrower hooks (`useGscSites`, `useGscSite`,
4
- // `useGscAnalyzer`, `useGscBootProgress`) — no global mutable `currentSite`,
5
- // no leaked progress writers.
6
-
7
- import type { SiteListItem } from '@gscdump/contracts'
8
- import { useGscAnalyticsClient } from './useGscAnalyticsClient'
9
-
10
- export type { SiteListItem }
11
-
12
- export type ReadBackend = 'd1' | 'r2'
13
-
14
- export interface SiteRecord extends SiteListItem {
15
- /** Canonical GSC site URL. Alias of `label` for back-compat. */
16
- siteUrl: string
17
- /**
18
- * Which backend serves analysis for this site. Carried through from the
19
- * sites endpoint; when omitted the value defaults to `'r2'` so legacy
20
- * consumers keep working. Browser-attach eligibility is ultimately
21
- * decided by the server's source provider via `browserAttachEligible`
22
- * on `/api/__gsc/sites/:siteId/source-info`, not by this field.
23
- */
24
- readBackend: ReadBackend
25
- syncStatus?: string | null
26
- }
27
-
28
- export type SiteLoadStage = 'idle' | 'wasm' | 'manifest' | 'attach' | 'ready' | 'error'
29
-
30
- export interface SiteLoadProgress {
31
- siteId: string
32
- stage: SiteLoadStage
33
- /** Sub-units (parquet files, rollup JSONs) completed. */
34
- filesAttached: number
35
- /** Sub-units expected; 0 while unknown. */
36
- filesTotal: number
37
- startedAt: number
38
- endedAt?: number
39
- error?: string
40
- source?: 'duckdb' | 'rollup' | string
41
- }
42
-
43
- export interface GscAnalyticsContext {
44
- sites: Ref<SiteListItem[] | null>
45
- sitesLoading: Readonly<Ref<boolean>>
46
- sitesError: Readonly<Ref<Error | null>>
47
- refreshSites: () => Promise<void>
48
- progress: Readonly<Ref<Record<string, SiteLoadProgress>>>
49
- /** Escape hatch for demos/tests — write raw progress entries. */
50
- patchProgress: (siteId: string, patch: Partial<SiteLoadProgress>) => void
51
- /** Escape hatch for demos/tests — clear progress map (all or one site). */
52
- clearProgress: (siteId?: string) => void
53
- }
54
-
55
- // Factory exposed so the layer's Nuxt plugin can provide a single app-wide
56
- // context via `provide: { gscAnalytics: createGscAnalyticsContext() }`.
57
- export function createGscAnalyticsContext(): GscAnalyticsContext {
58
- return createContext()
59
- }
60
-
61
- /** Resolve the shared context. The layer's plugin guarantees this is provided. */
62
- export function useGscAnalyticsContext(): GscAnalyticsContext {
63
- return useNuxtApp().$gscAnalytics
64
- }
65
-
66
- /** Read-only sites list + refresh. */
67
- export function useGscSites(): {
68
- sites: Ref<SiteListItem[] | null>
69
- loading: Readonly<Ref<boolean>>
70
- error: Readonly<Ref<Error | null>>
71
- refresh: () => Promise<void>
72
- } {
73
- const { sites, sitesLoading, sitesError, refreshSites } = useGscAnalyticsContext()
74
- return { sites, loading: sitesLoading, error: sitesError, refresh: refreshSites }
75
- }
76
-
77
- /** Derived site record for a given id. Non-mutating; returns `null` until the list resolves. */
78
- export function useGscSite(siteId: MaybeRefOrGetter<string | null | undefined>): Ref<SiteRecord | null> {
79
- const { sites } = useGscAnalyticsContext()
80
- return computed(() => {
81
- const id = toValue(siteId)
82
- if (!id)
83
- return null
84
- const found = sites.value?.find((s: SiteListItem) => s.id === id)
85
- if (found)
86
- return toSiteRecord(found)
87
- // List not yet loaded — stamp a minimal record so downstream queries can
88
- // dispatch against the id. Gets upgraded when sites resolves.
89
- return {
90
- id,
91
- label: id,
92
- hostname: id,
93
- propertyType: 'url-prefix',
94
- siteUrl: id,
95
- readBackend: 'r2',
96
- }
97
- })
98
- }
99
-
100
- function toSiteRecord(s: SiteListItem): SiteRecord {
101
- return {
102
- ...s,
103
- siteUrl: s.label,
104
- readBackend: s.readBackend ?? 'r2',
105
- }
106
- }
107
-
108
- /** Read-only boot-progress map for UI indicators. */
109
- export function useGscBootProgress(): {
110
- progress: Readonly<Ref<Record<string, SiteLoadProgress>>>
111
- entries: ComputedRef<SiteLoadProgress[]>
112
- allReady: ComputedRef<boolean>
113
- anyActive: ComputedRef<boolean>
114
- } {
115
- const { progress } = useGscAnalyticsContext()
116
- const entries = computed<SiteLoadProgress[]>(() => {
117
- const all = Object.values(progress.value) as SiteLoadProgress[]
118
- return all.sort((a, b) => a.startedAt - b.startedAt)
119
- })
120
- const allReady = computed(() =>
121
- entries.value.length > 0 && entries.value.every((s: SiteLoadProgress) => s.stage === 'ready'),
122
- )
123
- const anyActive = computed(() =>
124
- entries.value.some((s: SiteLoadProgress) => s.stage !== 'ready' && s.stage !== 'idle' && s.stage !== 'error'),
125
- )
126
- return { progress, entries, allReady, anyActive }
127
- }
128
-
129
- function createContext(): GscAnalyticsContext {
130
- const sites = ref<SiteListItem[] | null>(null)
131
- const sitesLoading = ref(false)
132
- const sitesError = ref<Error | null>(null)
133
- const progress = ref<Record<string, SiteLoadProgress>>({})
134
-
135
- async function refreshSites(): Promise<void> {
136
- sitesLoading.value = true
137
- sitesError.value = null
138
- sites.value = await useGscAnalyticsClient().listSites().catch((err) => {
139
- sitesError.value = err instanceof Error ? err : new Error(String(err))
140
- return null
141
- })
142
- sitesLoading.value = false
143
- }
144
-
145
- function patchProgress(siteId: string, patch: Partial<SiteLoadProgress>): void {
146
- const prev = progress.value[siteId]
147
- progress.value = {
148
- ...progress.value,
149
- [siteId]: {
150
- siteId,
151
- stage: 'idle',
152
- filesAttached: 0,
153
- filesTotal: 0,
154
- startedAt: prev?.startedAt ?? Date.now(),
155
- ...prev,
156
- ...patch,
157
- },
158
- }
159
- }
160
-
161
- function clearProgress(siteId?: string): void {
162
- if (!siteId) {
163
- progress.value = {}
164
- return
165
- }
166
- const next = { ...progress.value }
167
- delete next[siteId]
168
- progress.value = next
169
- }
170
-
171
- // Lazy-load: defer refreshSites until something actually reads `sites`.
172
- // Eager kick-off used to race host plugins that set auth via `setGscAuth`;
173
- // first call would 401 and the rest of the session would silently degrade.
174
- // Triggering on first read still needs to wait until hydration is done:
175
- // `refreshSites()` flips `sitesLoading` synchronously, and doing that during
176
- // the client's first render makes the sidebar disagree with SSR markup.
177
- let kickedOff = false
178
- function maybeKickOff(): void {
179
- if (kickedOff || !import.meta.client)
180
- return
181
- kickedOff = true
182
- const start = (): void => {
183
- void refreshSites()
184
- }
185
- const nuxtApp = useNuxtApp()
186
- if (nuxtApp.isHydrating)
187
- onNuxtReady(start)
188
- else
189
- queueMicrotask(start)
190
- }
191
- const lazySites = computed<SiteListItem[] | null>(() => {
192
- maybeKickOff()
193
- return sites.value
194
- })
195
-
196
- return {
197
- sites: lazySites as Readonly<Ref<SiteListItem[] | null>> as unknown as Ref<SiteListItem[] | null>,
198
- sitesLoading: sitesLoading as Readonly<Ref<boolean>>,
199
- sitesError: sitesError as Readonly<Ref<Error | null>>,
200
- refreshSites,
201
- progress: progress as Readonly<Ref<Record<string, SiteLoadProgress>>>,
202
- patchProgress,
203
- clearProgress,
204
- }
205
- }
@@ -1,9 +0,0 @@
1
- // Reader for the layer's `AnalyticsClient`. Built once per NuxtApp by the
2
- // layer plugin and provided as `$gscAnalyticsClient`. Hosts override by
3
- // providing their own from a later plugin (e.g. to swap the SDK transport).
4
-
5
- import type { AnalyticsClient } from '@gscdump/sdk'
6
-
7
- export function useGscAnalyticsClient(): AnalyticsClient {
8
- return useNuxtApp().$gscAnalyticsClient as AnalyticsClient
9
- }
@@ -1,8 +0,0 @@
1
- // Typed reader for `runtimeConfig.public.analytics`. Single accessor so leaf
2
- // composables stop re-typing the cast and silently drift when keys are added.
3
-
4
- import type { GscAnalyticsRuntimeConfig } from '../../types'
5
-
6
- export function useGscAnalyticsConfig(): GscAnalyticsRuntimeConfig {
7
- return useRuntimeConfig().public.analytics as unknown as GscAnalyticsRuntimeConfig
8
- }
@@ -1,159 +0,0 @@
1
- // Per-site reactive probe of what the server-resolved AnalysisQuerySource
2
- // can do. The layer exposes source shape (kind + analyzer set); tier logic
3
- // lives in the host.
4
- //
5
- // Use this to render locked/ghost UI upfront without issuing a doomed
6
- // analyze() call for each panel.
7
- //
8
- // The reactive composable and the non-reactive `loadSourceInfoFor` share one
9
- // entry per site/searchType/range via the shared site-resource seam, so they
10
- // collapse to one network read per session slice.
11
-
12
- import type { SourceCapabilities } from '@gscdump/analysis'
13
- import type { SourceInfoOptions } from '@gscdump/contracts'
14
- import { acquireSharedEntry, useGscSharedSiteResource } from './_useGscSharedSiteResource'
15
- import { useGscAnalyticsClient } from './useGscAnalyticsClient'
16
-
17
- export interface GscAnalyticsSourceInfo {
18
- /** Source implementation name — e.g. "gsc-api", "engine", "browser". */
19
- name: string
20
- /** `row` = GSC API / in-memory; `sql` = DuckDB/SQLite-backed. */
21
- kind: 'row' | 'sql'
22
- capabilities: SourceCapabilities
23
- /** Analyzer ids runnable against this source, from the default registry. */
24
- supportedAnalyzerIds: string[]
25
- /** Host declared attach hint — client should boot DuckDB-WASM and attach parquets. */
26
- browserAttachEligible: boolean
27
- /**
28
- * Host-controlled identity attrs echoed back for debug/display (e.g.
29
- * `{ tier: 'free' | 'pro' }`). Layer treats as opaque — never use these
30
- * for gating on the client, the source provider is the gate.
31
- */
32
- identityAttrs?: Record<string, unknown> | null
33
- }
34
-
35
- interface GscAnalyticsSourceInfoState {
36
- info: Ref<GscAnalyticsSourceInfo | null>
37
- loading: Ref<boolean>
38
- error: Ref<Error | null>
39
- refresh: () => Promise<void>
40
- /** Convenience predicate for panel gating: true when the analyzer id is in `supportedAnalyzerIds`. */
41
- supports: (analyzerId: MaybeRefOrGetter<string>) => ComputedRef<boolean>
42
- }
43
-
44
- interface SourceInfoEntry {
45
- info: Ref<GscAnalyticsSourceInfo | null>
46
- loading: Ref<boolean>
47
- error: Ref<Error | null>
48
- pending: Ref<Promise<void> | null>
49
- }
50
-
51
- const SOURCE_INFO_NAMESPACE = 'source-info'
52
- const DEFAULT_SEARCH_TYPE = 'web'
53
-
54
- function createEntry(): SourceInfoEntry {
55
- return {
56
- info: ref<GscAnalyticsSourceInfo | null>(null),
57
- loading: ref(false),
58
- error: ref<Error | null>(null),
59
- pending: shallowRef<Promise<void> | null>(null),
60
- }
61
- }
62
-
63
- function sourceInfoKey(siteId: string, options?: SourceInfoOptions): string {
64
- return JSON.stringify([
65
- siteId,
66
- options?.searchType ?? DEFAULT_SEARCH_TYPE,
67
- options?.start ?? options?.startDate ?? null,
68
- options?.end ?? options?.endDate ?? null,
69
- ])
70
- }
71
-
72
- function fetchInto(entry: SourceInfoEntry, siteId: string, options?: SourceInfoOptions): Promise<void> {
73
- if (entry.pending.value)
74
- return entry.pending.value
75
- entry.loading.value = true
76
- entry.error.value = null
77
- const p = (useGscAnalyticsClient().getSourceInfo(siteId, options) as Promise<GscAnalyticsSourceInfo>)
78
- .then((data) => {
79
- entry.info.value = data
80
- })
81
- .catch((err: unknown) => {
82
- entry.error.value = err instanceof Error ? err : new Error(String(err))
83
- })
84
- .finally(() => {
85
- entry.loading.value = false
86
- entry.pending.value = null
87
- })
88
- entry.pending.value = p
89
- return p
90
- }
91
-
92
- /**
93
- * Non-reactive source-info loader for callers outside a Vue scope (e.g. the
94
- * analyzer's boot IIFE in `useGscAnalyzer.createInstance`). Shares the same
95
- * per-site cache `useGscAnalyticsSourceInfo` consumes via the shared
96
- * site-resource seam, so gating UI and the analyzer mode probe collapse to
97
- * one network read per site per session.
98
- */
99
- export async function loadSourceInfoFor(siteId: string, options?: SourceInfoOptions): Promise<GscAnalyticsSourceInfo> {
100
- const entry = acquireSharedEntry(SOURCE_INFO_NAMESPACE, sourceInfoKey(siteId, options), createEntry)
101
- if (entry.info.value)
102
- return entry.info.value
103
- await fetchInto(entry, siteId, options)
104
- if (entry.error.value)
105
- throw entry.error.value
106
- if (!entry.info.value)
107
- throw new Error(`loadSourceInfoFor(${siteId}): no info after fetch`)
108
- return entry.info.value
109
- }
110
-
111
- export function useGscAnalyticsSourceInfo(
112
- siteId: MaybeRefOrGetter<string | null | undefined>,
113
- options: MaybeRefOrGetter<SourceInfoOptions | null | undefined> = null,
114
- ): GscAnalyticsSourceInfoState {
115
- const cacheKey = computed(() => {
116
- const id = toValue(siteId)
117
- return id ? sourceInfoKey(id, toValue(options) ?? undefined) : null
118
- })
119
- const { bound } = useGscSharedSiteResource<SourceInfoEntry>(SOURCE_INFO_NAMESPACE, cacheKey, {
120
- factory: () => createEntry(),
121
- // No onDispose: source-info is cheap and persistent across the session.
122
- })
123
-
124
- if (import.meta.client) {
125
- watch(bound, (entry: SourceInfoEntry | null) => {
126
- if (!entry)
127
- return
128
- const id = toValue(siteId)
129
- if (!id)
130
- return
131
- if (entry.info.value == null && entry.pending.value == null && entry.error.value == null)
132
- void fetchInto(entry, id, toValue(options) ?? undefined)
133
- }, { immediate: true })
134
- }
135
-
136
- const info = computed(() => bound.value?.info.value ?? null) as unknown as Ref<GscAnalyticsSourceInfo | null>
137
- const loading = computed(() => bound.value?.loading.value ?? false) as unknown as Ref<boolean>
138
- const error = computed(() => bound.value?.error.value ?? null) as unknown as Ref<Error | null>
139
-
140
- async function refresh(): Promise<void> {
141
- const entry = bound.value
142
- const id = toValue(siteId)
143
- if (!entry || !id)
144
- return
145
- entry.info.value = null
146
- await fetchInto(entry, id, toValue(options) ?? undefined)
147
- }
148
-
149
- function supports(analyzerId: MaybeRefOrGetter<string>): ComputedRef<boolean> {
150
- return computed(() => {
151
- const list = bound.value?.info.value?.supportedAnalyzerIds
152
- if (!list)
153
- return false
154
- return list.includes(toValue(analyzerId))
155
- })
156
- }
157
-
158
- return { info, loading, error, refresh, supports }
159
- }