@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,202 +0,0 @@
1
- // Site-keyed async resource composable. Owns key-watch, status classification,
2
- // refresh, and cache policy for every read-only `/api/__gsc/*` operation in
3
- // the layer. Resource composables stay thin adapters that wire reactive keys
4
- // to `gscQueries.*(...)` operations (preferred) — or to a `fetcher` for the
5
- // rollup composables that mix multiple operations + progress side-effects.
6
-
7
- import type { ComputedRef, Ref, WatchSource } from '@vue/runtime-core'
8
- import type { NuxtRpcQueryOperation } from 'nuxt-use-query/rpc'
9
- import type { GscErrorStatus } from '../utils/gsc-error'
10
- import { useNuxtRpcQuery } from 'nuxt-use-query/rpc'
11
- import { classifyGscError } from '../utils/gsc-error'
12
- import { useGscFetch } from '../utils/gsc-fetch'
13
-
14
- export type GscResourceStatus = 'idle' | 'pending' | 'success' | 'empty' | GscErrorStatus
15
-
16
- export interface UseGscResourceOptions<TArgs extends readonly unknown[], TData> {
17
- /** Reactive args passed to the fetcher. Resource stays idle while any required key is null/undefined. */
18
- keys: { [I in keyof TArgs]: MaybeRefOrGetter<TArgs[I] | null | undefined> }
19
- /** RPC query operation invoked with the resolved keys. Preferred over `fetcher` — gets Zod validation + shared cache keying. */
20
- operation?: (...args: TArgs) => NuxtRpcQueryOperation<any, any>
21
- /** Composite async fetcher invoked with the resolved keys. Use only when an operation can't model the request (multi-call, progress side-effects). */
22
- fetcher?: (...args: TArgs) => Promise<TData | null>
23
- /** Predicate for the `empty` status — defaults to checking `null`/`[]`/typical container fields. */
24
- isEmpty?: (data: TData) => boolean
25
- /** Extra reactive sources that should retrigger a fetch. */
26
- watchSources?: WatchSource[]
27
- /** Optional namespace for the fetcher-mode cache key. Defaults to `gsc-resource`. Ignored when `operation` is provided (the operation owns its key). */
28
- namespace?: string
29
- /** Milliseconds before cached data is considered stale. Defaults to `nuxt-use-query`'s 0. */
30
- staleTime?: number
31
- /** Evict cached payload after the last consumer unmounts. Defaults to `nuxt-use-query`'s 5 min. */
32
- gcTime?: number
33
- }
34
-
35
- export interface UseGscResourceReturn<TData> {
36
- data: Readonly<Ref<TData | null>>
37
- status: Readonly<Ref<GscResourceStatus>>
38
- loading: ComputedRef<boolean>
39
- error: Readonly<Ref<Error | null>>
40
- refresh: () => Promise<void>
41
- }
42
-
43
- function defaultIsEmpty(v: unknown): boolean {
44
- if (v == null)
45
- return true
46
- if (Array.isArray(v))
47
- return v.length === 0
48
- if (typeof v === 'object') {
49
- const rec = v as Record<string, unknown>
50
- for (const key of ['rows', 'records', 'snapshots', 'items', 'results']) {
51
- const arr = rec[key]
52
- if (Array.isArray(arr))
53
- return arr.length === 0
54
- }
55
- }
56
- return false
57
- }
58
-
59
- export function useGscResource<TArgs extends readonly unknown[], TData>(
60
- opts: UseGscResourceOptions<TArgs, TData>,
61
- ): UseGscResourceReturn<TData> {
62
- const namespace = opts.namespace ?? 'gsc-resource'
63
- const resolvedArgs = computed<TArgs | null>(() => {
64
- const out: unknown[] = []
65
- for (const k of opts.keys) {
66
- const v = toValue(k)
67
- if (v == null || v === '')
68
- return null
69
- out.push(v)
70
- }
71
- return out as unknown as TArgs
72
- })
73
-
74
- const enabled = computed(() => resolvedArgs.value != null)
75
- const gscFetch = useGscFetch()
76
-
77
- // Operation branch: route through useNuxtRpcQuery so we inherit Zod
78
- // response validation, RPC key serialization, and shared cache invalidation.
79
- if (opts.operation) {
80
- const operation = computed<NuxtRpcQueryOperation<any, any> | null>(() => {
81
- const args = resolvedArgs.value
82
- return args ? opts.operation!(...args) : null
83
- })
84
-
85
- // Idle placeholder when args aren't ready — useNuxtRpcQuery is always
86
- // mounted, but the `enabled` gate prevents the request firing.
87
- const fallbackOp = { key: `${namespace}:idle`, path: `${namespace}:idle`, response: { parse: (v: unknown) => v } as any }
88
-
89
- const query = useNuxtRpcQuery<any>(
90
- () => operation.value ?? fallbackOp,
91
- {
92
- enabled,
93
- watch: opts.watchSources,
94
- staleTime: opts.staleTime,
95
- gcTime: opts.gcTime,
96
- $fetch: gscFetch as any,
97
- } as any,
98
- )
99
-
100
- const data = computed<TData | null>(() => {
101
- if (!enabled.value)
102
- return null
103
- return (query.displayData.value ?? null) as TData | null
104
- })
105
-
106
- const error = computed<Error | null>(() => {
107
- if (!enabled.value)
108
- return null
109
- const e = query.error.value
110
- return e == null ? null : e instanceof Error ? e : new Error(String(e))
111
- })
112
-
113
- const status = computed<GscResourceStatus>(() => {
114
- if (!enabled.value)
115
- return 'idle'
116
- if (query.status.value === 'pending')
117
- return 'pending'
118
- if (query.status.value === 'error') {
119
- const classified = classifyGscError(error.value)
120
- return classified.status
121
- }
122
- const out = data.value
123
- if (query.status.value === 'success')
124
- return out == null || (opts.isEmpty ?? defaultIsEmpty)(out) ? 'empty' : 'success'
125
- return 'idle'
126
- })
127
-
128
- const loading = computed(() => status.value === 'pending')
129
-
130
- async function refresh(): Promise<void> {
131
- if (!enabled.value)
132
- return
133
- await query.refresh()
134
- }
135
-
136
- return { data, status, loading, error, refresh }
137
- }
138
-
139
- // Fetcher branch: composables that can't be modelled as a single RPC
140
- // operation (rollup fan-out, progress tracking) drop down to a raw
141
- // useNuxtQuery + bound $fetch.
142
- const queryKey = computed(() => {
143
- const args = resolvedArgs.value
144
- return args ? `${namespace}:${JSON.stringify(args)}` : `${namespace}:idle`
145
- })
146
-
147
- const fetchResource = (async () => {
148
- const args = resolvedArgs.value
149
- if (!args)
150
- return null
151
- if (opts.fetcher)
152
- return await opts.fetcher(...args)
153
- throw new Error('useGscResource: no operation or fetcher configured')
154
- }) as unknown as typeof $fetch
155
-
156
- const query = useNuxtQuery<TData | null, Error>(() => queryKey.value, {
157
- key: () => queryKey.value,
158
- enabled,
159
- watch: opts.watchSources,
160
- staleTime: opts.staleTime,
161
- gcTime: opts.gcTime,
162
- $fetch: fetchResource,
163
- })
164
-
165
- const data = computed<TData | null>(() => {
166
- if (!enabled.value)
167
- return null
168
- return (query.displayData.value ?? null) as TData | null
169
- })
170
-
171
- const error = computed<Error | null>(() => {
172
- if (!enabled.value)
173
- return null
174
- const e = query.error.value
175
- return e == null ? null : e instanceof Error ? e : new Error(String(e))
176
- })
177
-
178
- const status = computed<GscResourceStatus>(() => {
179
- if (!enabled.value)
180
- return 'idle'
181
- if (query.status.value === 'pending')
182
- return 'pending'
183
- if (query.status.value === 'error') {
184
- const classified = classifyGscError(error.value)
185
- return classified.status
186
- }
187
- const out = data.value
188
- if (query.status.value === 'success')
189
- return out == null || (opts.isEmpty ?? defaultIsEmpty)(out) ? 'empty' : 'success'
190
- return 'idle'
191
- })
192
-
193
- const loading = computed(() => status.value === 'pending')
194
-
195
- async function refresh(): Promise<void> {
196
- if (!enabled.value)
197
- return
198
- await query.refresh()
199
- }
200
-
201
- return { data, status, loading, error, refresh }
202
- }
@@ -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,206 +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 client = useGscAnalyticsClient()
131
- const sites = ref<SiteListItem[] | null>(null)
132
- const sitesLoading = ref(false)
133
- const sitesError = ref<Error | null>(null)
134
- const progress = ref<Record<string, SiteLoadProgress>>({})
135
-
136
- async function refreshSites(): Promise<void> {
137
- sitesLoading.value = true
138
- sitesError.value = null
139
- sites.value = await client.listSites().catch((err: unknown) => {
140
- sitesError.value = err instanceof Error ? err : new Error(String(err))
141
- return null
142
- })
143
- sitesLoading.value = false
144
- }
145
-
146
- function patchProgress(siteId: string, patch: Partial<SiteLoadProgress>): void {
147
- const prev = progress.value[siteId]
148
- progress.value = {
149
- ...progress.value,
150
- [siteId]: {
151
- siteId,
152
- stage: 'idle',
153
- filesAttached: 0,
154
- filesTotal: 0,
155
- startedAt: prev?.startedAt ?? Date.now(),
156
- ...prev,
157
- ...patch,
158
- },
159
- }
160
- }
161
-
162
- function clearProgress(siteId?: string): void {
163
- if (!siteId) {
164
- progress.value = {}
165
- return
166
- }
167
- const next = { ...progress.value }
168
- delete next[siteId]
169
- progress.value = next
170
- }
171
-
172
- // Lazy-load: defer refreshSites until something actually reads `sites`.
173
- // Eager kick-off used to race host plugins that set auth via `setGscAuth`;
174
- // first call would 401 and the rest of the session would silently degrade.
175
- // Triggering on first read still needs to wait until hydration is done:
176
- // `refreshSites()` flips `sitesLoading` synchronously, and doing that during
177
- // the client's first render makes the sidebar disagree with SSR markup.
178
- let kickedOff = false
179
- function maybeKickOff(): void {
180
- if (kickedOff || !import.meta.client)
181
- return
182
- kickedOff = true
183
- const start = (): void => {
184
- void refreshSites()
185
- }
186
- const nuxtApp = useNuxtApp()
187
- if (nuxtApp.isHydrating)
188
- onNuxtReady(start)
189
- else
190
- queueMicrotask(start)
191
- }
192
- const lazySites = computed<SiteListItem[] | null>(() => {
193
- maybeKickOff()
194
- return sites.value
195
- })
196
-
197
- return {
198
- sites: lazySites as Readonly<Ref<SiteListItem[] | null>> as unknown as Ref<SiteListItem[] | null>,
199
- sitesLoading: sitesLoading as Readonly<Ref<boolean>>,
200
- sitesError: sitesError as Readonly<Ref<Error | null>>,
201
- refreshSites,
202
- progress: progress as Readonly<Ref<Record<string, SiteLoadProgress>>>,
203
- patchProgress,
204
- clearProgress,
205
- }
206
- }
@@ -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
- }