@gscdump/nuxt 0.21.3 → 0.22.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +36 -27
- package/app/components/GscDashboardPage.vue +2 -2
- package/app/components/GscHero.vue +3 -4
- package/app/components/GscLazyTopResult.vue +7 -5
- package/app/components/GscPerformanceChart.vue +4 -4
- package/app/components/GscSourceDebugPanel.vue +2 -1
- package/app/composables/_useGscBackfill.ts +13 -5
- package/app/composables/_useGscQueryDispatcher.ts +5 -4
- package/app/composables/_useGscResource.ts +144 -56
- package/app/composables/useGscAnalytics.ts +13 -4
- package/app/composables/useGscAnalyticsSourceInfo.ts +11 -5
- package/app/composables/useGscAnalyzer.ts +33 -25
- package/app/composables/useGscAnalyzerBatch.ts +2 -2
- package/app/composables/useGscAnalyzerDefs.ts +2 -1
- package/app/composables/useGscCountries.ts +5 -6
- package/app/composables/useGscCurrentSite.ts +32 -11
- package/app/composables/useGscInspectionHistory.ts +5 -5
- package/app/composables/useGscInspections.ts +5 -5
- package/app/composables/useGscPanelContext.ts +9 -2
- package/app/composables/useGscQuery.ts +6 -8
- package/app/composables/useGscQueryDispatcher.ts +14 -122
- package/app/composables/useGscRequestIndexingInspect.ts +20 -11
- package/app/composables/useGscResource.ts +11 -114
- package/app/composables/useGscRollup.ts +66 -81
- package/app/composables/useGscSearchAppearance.ts +5 -6
- package/app/composables/useGscSiteAnalyzer.ts +456 -0
- package/app/composables/useGscSitemapHistory.ts +5 -5
- package/app/composables/useGscSitemaps.ts +5 -5
- package/app/queries/gsc.ts +163 -0
- package/app/utils/duckdb-wasm.ts +166 -0
- package/app/utils/gsc-rpc.ts +18 -0
- package/module.ts +1 -1
- package/nuxt.config.ts +25 -0
- package/package.json +140 -8
- package/types.ts +8 -7
- package/app/composables/useGscEngine.ts +0 -16
- package/app/composables/useGscParquetTable.ts +0 -199
- package/app/composables/useGscRollupTable.ts +0 -78
- package/app/composables/useGscRowQuery.ts +0 -56
- package/app/composables/useGscSnapshotAnalyzer.contract.ts +0 -150
- package/app/composables/useGscSnapshotAnalyzer.routing.ts +0 -87
- package/app/composables/useGscSnapshotAnalyzer.ts +0 -559
- package/app/utils/setup-gsc-fetch-auth.ts +0 -62
|
@@ -1,114 +1,11 @@
|
|
|
1
|
-
//
|
|
2
|
-
//
|
|
3
|
-
//
|
|
4
|
-
//
|
|
5
|
-
//
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
import type { ComputedRef, Ref, WatchSource } from 'vue'
|
|
13
|
-
import type { GscErrorStatus } from '../utils/gsc-error'
|
|
14
|
-
import { classifyGscError } from '../utils/gsc-error'
|
|
15
|
-
|
|
16
|
-
export type GscResourceStatus = 'idle' | 'pending' | 'success' | 'empty' | GscErrorStatus
|
|
17
|
-
|
|
18
|
-
export interface UseGscResourceOptions<TArgs extends readonly unknown[], TData> {
|
|
19
|
-
/** Reactive args passed to the fetcher. Resource stays idle while any required key is null/undefined. */
|
|
20
|
-
keys: { [I in keyof TArgs]: MaybeRefOrGetter<TArgs[I] | null | undefined> }
|
|
21
|
-
/** Async fetcher invoked with the resolved keys. */
|
|
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
|
-
}
|
|
28
|
-
|
|
29
|
-
export interface UseGscResourceReturn<TData> {
|
|
30
|
-
data: Ref<TData | null>
|
|
31
|
-
status: Ref<GscResourceStatus>
|
|
32
|
-
loading: ComputedRef<boolean>
|
|
33
|
-
error: Ref<Error | null>
|
|
34
|
-
refresh: () => Promise<void>
|
|
35
|
-
}
|
|
36
|
-
|
|
37
|
-
function defaultIsEmpty(v: unknown): boolean {
|
|
38
|
-
if (v == null)
|
|
39
|
-
return true
|
|
40
|
-
if (Array.isArray(v))
|
|
41
|
-
return v.length === 0
|
|
42
|
-
if (typeof v === 'object') {
|
|
43
|
-
const rec = v as Record<string, unknown>
|
|
44
|
-
for (const key of ['rows', 'records', 'snapshots', 'items', 'results']) {
|
|
45
|
-
const arr = rec[key]
|
|
46
|
-
if (Array.isArray(arr))
|
|
47
|
-
return arr.length === 0
|
|
48
|
-
}
|
|
49
|
-
}
|
|
50
|
-
return false
|
|
51
|
-
}
|
|
52
|
-
|
|
53
|
-
export function useGscResource<TArgs extends readonly unknown[], TData>(
|
|
54
|
-
opts: UseGscResourceOptions<TArgs, TData>,
|
|
55
|
-
): UseGscResourceReturn<TData> {
|
|
56
|
-
const data = shallowRef<TData | null>(null)
|
|
57
|
-
const status = ref<GscResourceStatus>('idle')
|
|
58
|
-
const error = ref<Error | null>(null)
|
|
59
|
-
const loading = computed(() => status.value === 'pending')
|
|
60
|
-
|
|
61
|
-
let runToken = 0
|
|
62
|
-
|
|
63
|
-
function resolveKeys(): 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
|
-
async function refresh(): Promise<void> {
|
|
75
|
-
const args = resolveKeys()
|
|
76
|
-
const token = ++runToken
|
|
77
|
-
if (!args) {
|
|
78
|
-
data.value = null
|
|
79
|
-
status.value = 'idle'
|
|
80
|
-
error.value = null
|
|
81
|
-
return
|
|
82
|
-
}
|
|
83
|
-
status.value = 'pending'
|
|
84
|
-
error.value = null
|
|
85
|
-
try {
|
|
86
|
-
const out = await opts.fetcher(...args)
|
|
87
|
-
if (token !== runToken)
|
|
88
|
-
return
|
|
89
|
-
data.value = out
|
|
90
|
-
const empty = out == null || (opts.isEmpty ?? defaultIsEmpty)(out)
|
|
91
|
-
status.value = empty ? 'empty' : 'success'
|
|
92
|
-
}
|
|
93
|
-
catch (e) {
|
|
94
|
-
if (token !== runToken)
|
|
95
|
-
return
|
|
96
|
-
const classified = classifyGscError(e)
|
|
97
|
-
error.value = e instanceof Error ? e : new Error(String(e))
|
|
98
|
-
status.value = classified.status
|
|
99
|
-
data.value = null
|
|
100
|
-
}
|
|
101
|
-
}
|
|
102
|
-
|
|
103
|
-
watch(
|
|
104
|
-
[...opts.keys.map(k => () => toValue(k)), ...(opts.watchSources ?? [])],
|
|
105
|
-
refresh,
|
|
106
|
-
{ immediate: true },
|
|
107
|
-
)
|
|
108
|
-
|
|
109
|
-
onScopeDispose(() => {
|
|
110
|
-
runToken++
|
|
111
|
-
})
|
|
112
|
-
|
|
113
|
-
return { data, status, loading, error, refresh }
|
|
114
|
-
}
|
|
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'
|
|
@@ -11,9 +11,10 @@
|
|
|
11
11
|
|
|
12
12
|
import type { RollupEnvelope } from '@gscdump/contracts'
|
|
13
13
|
import type { SiteListItem } from './useGscAnalytics'
|
|
14
|
+
import { gscQueries } from '../queries/gsc'
|
|
15
|
+
import { useGscRpc } from '../utils/gsc-rpc'
|
|
14
16
|
import { useGscResource } from './_useGscResource'
|
|
15
17
|
import { useGscAnalyticsContext } from './useGscAnalytics'
|
|
16
|
-
import { useGscAnalyticsClient } from './useGscAnalyticsClient'
|
|
17
18
|
|
|
18
19
|
type RollupsInput = MaybeRefOrGetter<string | readonly string[]>
|
|
19
20
|
type SiteLike = string | { id: string } | SiteListItem
|
|
@@ -62,9 +63,11 @@ export function useGscRollup<T = unknown>(
|
|
|
62
63
|
opts: UseGscRollupOptions = {},
|
|
63
64
|
): UseGscRollupReturn<T> {
|
|
64
65
|
const ctx = useGscAnalyticsContext()
|
|
66
|
+
const rpc = useGscRpc()
|
|
65
67
|
const resource = useGscResource<[string, string], RollupEnvelope<T> | null>({
|
|
68
|
+
namespace: 'gsc-rollup',
|
|
66
69
|
keys: [siteId, rollupId],
|
|
67
|
-
fetcher: (id, rid) => fetchOne<T>(id, rid, ctx, toValue(opts.range) ?? null),
|
|
70
|
+
fetcher: (id, rid) => fetchOne<T>(rpc, id, rid, ctx, toValue(opts.range) ?? null),
|
|
68
71
|
watchSources: [() => toValue(opts.range)?.start, () => toValue(opts.range)?.end],
|
|
69
72
|
isEmpty: env => env == null,
|
|
70
73
|
})
|
|
@@ -84,43 +87,31 @@ export function useGscRollups<T = unknown>(
|
|
|
84
87
|
opts: UseGscRollupOptions = {},
|
|
85
88
|
): UseGscRollupsReturn<T> {
|
|
86
89
|
const ctx = useGscAnalyticsContext()
|
|
87
|
-
|
|
88
|
-
const
|
|
89
|
-
const
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
return
|
|
97
|
-
}
|
|
98
|
-
loading.value = true
|
|
99
|
-
const range = toValue(opts.range) ?? null
|
|
100
|
-
const next: Record<string, RollupEnvelope<T> | null> = {}
|
|
101
|
-
try {
|
|
90
|
+
const rpc = useGscRpc()
|
|
91
|
+
const rollupKey = computed(() => encodeStringList(normaliseRollups(toValue(rollupIds))))
|
|
92
|
+
const resource = useGscResource<[string, string], Record<string, RollupEnvelope<T> | null>>({
|
|
93
|
+
namespace: 'gsc-rollups',
|
|
94
|
+
keys: [siteId, rollupKey],
|
|
95
|
+
fetcher: async (sid, encoded) => {
|
|
96
|
+
const rids = decodeStringList(encoded)
|
|
97
|
+
const next: Record<string, RollupEnvelope<T> | null> = {}
|
|
98
|
+
const range = toValue(opts.range) ?? null
|
|
102
99
|
await Promise.all(rids.map(async (rid) => {
|
|
103
|
-
next[rid] = await fetchOne<T>(sid, rid, ctx, range)
|
|
100
|
+
next[rid] = await fetchOne<T>(rpc, sid, rid, ctx, range)
|
|
104
101
|
}))
|
|
105
|
-
|
|
106
|
-
}
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
}
|
|
111
|
-
|
|
112
|
-
watch(
|
|
113
|
-
() => [toValue(siteId), normaliseRollups(toValue(rollupIds)).join(','), toValue(opts.range)?.start, toValue(opts.range)?.end],
|
|
114
|
-
refresh,
|
|
115
|
-
{ immediate: true },
|
|
116
|
-
)
|
|
102
|
+
return next
|
|
103
|
+
},
|
|
104
|
+
watchSources: [() => toValue(opts.range)?.start, () => toValue(opts.range)?.end],
|
|
105
|
+
isEmpty: value => Object.keys(value).length === 0,
|
|
106
|
+
})
|
|
107
|
+
const envelopes = computed<Record<string, RollupEnvelope<T> | null>>(() => resource.data.value ?? {})
|
|
117
108
|
|
|
118
109
|
return {
|
|
119
110
|
get: (rollupId: string) => envelopes.value[rollupId] ?? null,
|
|
120
111
|
payload: (rollupId: string) => envelopes.value[rollupId]?.payload ?? null,
|
|
121
|
-
envelopes,
|
|
122
|
-
loading: loading as Readonly<Ref<boolean>>,
|
|
123
|
-
refresh,
|
|
112
|
+
envelopes: envelopes as unknown as Ref<Record<string, RollupEnvelope<T> | null>>,
|
|
113
|
+
loading: resource.loading as unknown as Readonly<Ref<boolean>>,
|
|
114
|
+
refresh: resource.refresh,
|
|
124
115
|
}
|
|
125
116
|
}
|
|
126
117
|
|
|
@@ -134,56 +125,39 @@ export function useGscRollupFanout<T = unknown>(
|
|
|
134
125
|
opts: UseGscRollupOptions = {},
|
|
135
126
|
): UseGscRollupFanoutReturn<T> {
|
|
136
127
|
const ctx = useGscAnalyticsContext()
|
|
137
|
-
const
|
|
138
|
-
const loading = ref(false)
|
|
128
|
+
const rpc = useGscRpc()
|
|
139
129
|
const progress = ref<{ completed: number, total: number }>({ completed: 0, total: 0 })
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
envelopes.value = {}
|
|
150
|
-
progress.value = { completed: 0, total: 0 }
|
|
151
|
-
return
|
|
152
|
-
}
|
|
153
|
-
loading.value = true
|
|
154
|
-
// Seed envelopes with nulls so the UI can render skeleton rows before any
|
|
155
|
-
// fetch resolves. Writes land incrementally as each site returns.
|
|
156
|
-
const seeded: Record<string, RollupEnvelope<T> | null> = {}
|
|
157
|
-
for (const sid of siteIds) seeded[sid] = null
|
|
158
|
-
envelopes.value = seeded
|
|
159
|
-
progress.value = { completed: 0, total: siteIds.length }
|
|
160
|
-
const range = toValue(opts.range) ?? null
|
|
161
|
-
try {
|
|
130
|
+
const siteKey = computed(() => encodeStringList(normaliseSites(toValue(sites))))
|
|
131
|
+
const resource = useGscResource<[string, string], Record<string, RollupEnvelope<T> | null>>({
|
|
132
|
+
namespace: 'gsc-rollup-fanout',
|
|
133
|
+
keys: [siteKey, rollupId],
|
|
134
|
+
fetcher: async (encoded, rid) => {
|
|
135
|
+
const siteIds = decodeStringList(encoded)
|
|
136
|
+
progress.value = { completed: 0, total: siteIds.length }
|
|
137
|
+
const range = toValue(opts.range) ?? null
|
|
138
|
+
const next: Record<string, RollupEnvelope<T> | null> = {}
|
|
162
139
|
await Promise.all(siteIds.map(async (sid) => {
|
|
163
|
-
|
|
164
|
-
if (token !== runToken)
|
|
165
|
-
return
|
|
166
|
-
envelopes.value = { ...envelopes.value, [sid]: env }
|
|
140
|
+
next[sid] = await fetchOne<T>(rpc, sid, rid, ctx, range)
|
|
167
141
|
progress.value = { completed: progress.value.completed + 1, total: siteIds.length }
|
|
168
142
|
}))
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
}
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
)
|
|
143
|
+
return next
|
|
144
|
+
},
|
|
145
|
+
watchSources: [() => toValue(opts.range)?.start, () => toValue(opts.range)?.end],
|
|
146
|
+
isEmpty: value => Object.keys(value).length === 0,
|
|
147
|
+
})
|
|
148
|
+
const envelopes = computed<Record<string, RollupEnvelope<T> | null>>(() => resource.data.value ?? {})
|
|
149
|
+
watch(resource.loading, (loading) => {
|
|
150
|
+
if (loading)
|
|
151
|
+
return
|
|
152
|
+
if (!resource.data.value)
|
|
153
|
+
progress.value = { completed: 0, total: 0 }
|
|
154
|
+
}, { immediate: true })
|
|
181
155
|
|
|
182
156
|
return {
|
|
183
|
-
envelopes,
|
|
184
|
-
loading: loading as Readonly<Ref<boolean>>,
|
|
157
|
+
envelopes: envelopes as unknown as Ref<Record<string, RollupEnvelope<T> | null>>,
|
|
158
|
+
loading: resource.loading as unknown as Readonly<Ref<boolean>>,
|
|
185
159
|
progress: progress as Readonly<Ref<{ completed: number, total: number }>>,
|
|
186
|
-
refresh,
|
|
160
|
+
refresh: resource.refresh,
|
|
187
161
|
}
|
|
188
162
|
}
|
|
189
163
|
|
|
@@ -204,7 +178,19 @@ function normaliseSites(input: unknown): string[] {
|
|
|
204
178
|
return out
|
|
205
179
|
}
|
|
206
180
|
|
|
181
|
+
function encodeStringList(values: string[]): string {
|
|
182
|
+
return values.length > 0 ? JSON.stringify(values) : ''
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
function decodeStringList(encoded: string): string[] {
|
|
186
|
+
const parsed = JSON.parse(encoded) as unknown
|
|
187
|
+
return Array.isArray(parsed)
|
|
188
|
+
? parsed.filter((v): v is string => typeof v === 'string' && v.length > 0)
|
|
189
|
+
: []
|
|
190
|
+
}
|
|
191
|
+
|
|
207
192
|
async function fetchOne<T>(
|
|
193
|
+
rpc: ReturnType<typeof useGscRpc>,
|
|
208
194
|
siteId: string,
|
|
209
195
|
rollupId: string,
|
|
210
196
|
ctx: ReturnType<typeof useGscAnalyticsContext>,
|
|
@@ -220,11 +206,10 @@ async function fetchOne<T>(
|
|
|
220
206
|
endedAt: undefined,
|
|
221
207
|
})
|
|
222
208
|
try {
|
|
223
|
-
const env = await
|
|
224
|
-
siteId,
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
)
|
|
209
|
+
const env = await rpc.query(
|
|
210
|
+
gscQueries.rollup(siteId, rollupId, range ?? undefined),
|
|
211
|
+
{ silent: true },
|
|
212
|
+
) as RollupEnvelope<T>
|
|
228
213
|
ctx.patchProgress(siteId, { stage: 'ready', filesAttached: 1, endedAt: Date.now() })
|
|
229
214
|
return env
|
|
230
215
|
}
|
|
@@ -5,8 +5,8 @@
|
|
|
5
5
|
import type { GscApiRange, SearchAppearanceResponse, SearchAppearanceRow } from '@gscdump/contracts'
|
|
6
6
|
import type { ComputedRef, Ref } from '@vue/runtime-core'
|
|
7
7
|
import type { GscResourceStatus } from './_useGscResource'
|
|
8
|
+
import { gscQueries } from '../queries/gsc'
|
|
8
9
|
import { useGscResource } from './_useGscResource'
|
|
9
|
-
import { useGscAnalyticsClient } from './useGscAnalyticsClient'
|
|
10
10
|
|
|
11
11
|
export interface UseGscSearchAppearanceReturn {
|
|
12
12
|
response: Readonly<Ref<SearchAppearanceResponse | null>>
|
|
@@ -14,8 +14,8 @@ export interface UseGscSearchAppearanceReturn {
|
|
|
14
14
|
range: ComputedRef<GscApiRange | null>
|
|
15
15
|
source: ComputedRef<SearchAppearanceResponse['source'] | null>
|
|
16
16
|
loading: ComputedRef<boolean>
|
|
17
|
-
status: Ref<GscResourceStatus
|
|
18
|
-
error: Ref<Error | null
|
|
17
|
+
status: Readonly<Ref<GscResourceStatus>>
|
|
18
|
+
error: Readonly<Ref<Error | null>>
|
|
19
19
|
refresh: () => Promise<void>
|
|
20
20
|
}
|
|
21
21
|
|
|
@@ -23,14 +23,13 @@ export function useGscSearchAppearance(
|
|
|
23
23
|
siteId: MaybeRefOrGetter<string | null | undefined>,
|
|
24
24
|
range: MaybeRefOrGetter<{ start: string, end: string } | null | undefined>,
|
|
25
25
|
): UseGscSearchAppearanceReturn {
|
|
26
|
-
const { data, status, loading, error, refresh } = useGscResource({
|
|
26
|
+
const { data, status, loading, error, refresh } = useGscResource<[string, string, string], SearchAppearanceResponse>({
|
|
27
27
|
keys: [
|
|
28
28
|
siteId,
|
|
29
29
|
() => toValue(range)?.start,
|
|
30
30
|
() => toValue(range)?.end,
|
|
31
31
|
] as const,
|
|
32
|
-
|
|
33
|
-
useGscAnalyticsClient().getSearchAppearance(id, { start, end }),
|
|
32
|
+
operation: (id, start, end) => gscQueries.searchAppearance(id, { start, end }),
|
|
34
33
|
isEmpty: r => r.rows.length === 0,
|
|
35
34
|
})
|
|
36
35
|
|