@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.
Files changed (43) hide show
  1. package/README.md +36 -27
  2. package/app/components/GscDashboardPage.vue +2 -2
  3. package/app/components/GscHero.vue +3 -4
  4. package/app/components/GscLazyTopResult.vue +7 -5
  5. package/app/components/GscPerformanceChart.vue +4 -4
  6. package/app/components/GscSourceDebugPanel.vue +2 -1
  7. package/app/composables/_useGscBackfill.ts +13 -5
  8. package/app/composables/_useGscQueryDispatcher.ts +5 -4
  9. package/app/composables/_useGscResource.ts +144 -56
  10. package/app/composables/useGscAnalytics.ts +13 -4
  11. package/app/composables/useGscAnalyticsSourceInfo.ts +11 -5
  12. package/app/composables/useGscAnalyzer.ts +33 -25
  13. package/app/composables/useGscAnalyzerBatch.ts +2 -2
  14. package/app/composables/useGscAnalyzerDefs.ts +2 -1
  15. package/app/composables/useGscCountries.ts +5 -6
  16. package/app/composables/useGscCurrentSite.ts +32 -11
  17. package/app/composables/useGscInspectionHistory.ts +5 -5
  18. package/app/composables/useGscInspections.ts +5 -5
  19. package/app/composables/useGscPanelContext.ts +9 -2
  20. package/app/composables/useGscQuery.ts +6 -8
  21. package/app/composables/useGscQueryDispatcher.ts +14 -122
  22. package/app/composables/useGscRequestIndexingInspect.ts +20 -11
  23. package/app/composables/useGscResource.ts +11 -114
  24. package/app/composables/useGscRollup.ts +66 -81
  25. package/app/composables/useGscSearchAppearance.ts +5 -6
  26. package/app/composables/useGscSiteAnalyzer.ts +456 -0
  27. package/app/composables/useGscSitemapHistory.ts +5 -5
  28. package/app/composables/useGscSitemaps.ts +5 -5
  29. package/app/queries/gsc.ts +163 -0
  30. package/app/utils/duckdb-wasm.ts +166 -0
  31. package/app/utils/gsc-rpc.ts +18 -0
  32. package/module.ts +1 -1
  33. package/nuxt.config.ts +25 -0
  34. package/package.json +140 -8
  35. package/types.ts +8 -7
  36. package/app/composables/useGscEngine.ts +0 -16
  37. package/app/composables/useGscParquetTable.ts +0 -199
  38. package/app/composables/useGscRollupTable.ts +0 -78
  39. package/app/composables/useGscRowQuery.ts +0 -56
  40. package/app/composables/useGscSnapshotAnalyzer.contract.ts +0 -150
  41. package/app/composables/useGscSnapshotAnalyzer.routing.ts +0 -87
  42. package/app/composables/useGscSnapshotAnalyzer.ts +0 -559
  43. package/app/utils/setup-gsc-fetch-auth.ts +0 -62
@@ -1,78 +0,0 @@
1
- // Top-N rollup table state for list pages.
2
- //
3
- // Owns the canonical wiring previously duplicated across consumer apps:
4
- // - shared `useGscPeriod` + windowed range
5
- // - `useGscRollup` fetch
6
- // - URL-synced search/sort via `useGscTableState`
7
- // - debounced fuzzy filter on one row field
8
- // - position-aware sort (sort key 'position' invokes `positionFor`)
9
- // - top-N slice (default 100)
10
- //
11
- // Row shape contract: `impressions` + `sum_position` so `positionFor` works
12
- // without a caller-supplied accessor.
13
-
14
- import type { MaybeRefOrGetter, Ref } from '@vue/runtime-core'
15
-
16
- interface SortState { column: string, direction: 'asc' | 'desc' }
17
-
18
- export interface UseGscRollupTableOptions<TRow> {
19
- siteId: MaybeRefOrGetter<string>
20
- rollupKey: string
21
- filterField: keyof TRow & string
22
- defaultSort?: SortState
23
- limit?: number
24
- debounceMs?: number
25
- }
26
-
27
- // eslint-disable-next-line ts/explicit-function-return-type -- complex generic inference; explicit type would diverge as composables evolve
28
- export function useGscRollupTable<
29
- TRow extends { impressions: number, sum_position: number },
30
- >(opts: UseGscRollupTableOptions<TRow>) {
31
- const { period, compareMode, stableData, range } = useGscPeriod()
32
- const windowRange = computed(() => ({ start: range.value.start, end: range.value.end }))
33
-
34
- const { data: payload, loading } = useGscRollup<TRow[]>(
35
- opts.siteId,
36
- opts.rollupKey,
37
- { range: windowRange },
38
- )
39
-
40
- const { q, sort, toggleSort } = useGscTableState({ defaultSort: opts.defaultSort })
41
-
42
- const searchDebounced = refDebounced<string>(q, opts.debounceMs ?? 150)
43
-
44
- const limit = opts.limit ?? 100
45
- const rows = computed<TRow[]>(() => {
46
- const needle = searchDebounced.value.trim().toLowerCase()
47
- const source = (payload.value ?? []).slice()
48
- const filtered = needle
49
- ? source.filter((r: TRow) => {
50
- const v = (r as Record<string, unknown>)[opts.filterField]
51
- return typeof v === 'string' && v.toLowerCase().includes(needle)
52
- })
53
- : source
54
- const s = sort.value
55
- if (s) {
56
- const dir = s.direction === 'desc' ? -1 : 1
57
- filtered.sort((a: TRow, b: TRow) => {
58
- const av = s.column === 'position' ? positionFor(a) : ((a as Record<string, unknown>)[s.column] as number)
59
- const bv = s.column === 'position' ? positionFor(b) : ((b as Record<string, unknown>)[s.column] as number)
60
- return av < bv ? -1 * dir : av > bv ? 1 * dir : 0
61
- })
62
- }
63
- return filtered.slice(0, limit)
64
- })
65
-
66
- return {
67
- period,
68
- compareMode,
69
- stableData,
70
- range,
71
- q,
72
- sort,
73
- toggleSort,
74
- payload: payload as Ref<TRow[] | null>,
75
- loading,
76
- rows,
77
- }
78
- }
@@ -1,56 +0,0 @@
1
- // Thin client wrapper over POST /api/__gsc/sites/[siteId]/rows. Takes a
2
- // BuilderState (or a typed query builder) and returns reactive rows with
3
- // auto-refetch when the state / site changes.
4
- //
5
- // Use for row-level page needs (country breakdowns, top-N with custom
6
- // filters, per-page drilldowns, …). For analyzer-style transforms, use
7
- // `useGscQuery` which dispatches via /analyze.
8
-
9
- import type { QueryRow } from '@gscdump/analysis'
10
- import type { GscRowQueryMeta, GscRowQueryResponse } from '@gscdump/contracts'
11
- import type { ComputedRef, Ref } from '@vue/runtime-core'
12
- import type { BuilderState } from 'gscdump/query'
13
- import { useGscResource } from './_useGscResource'
14
- import { useGscAnalyticsClient } from './useGscAnalyticsClient'
15
-
16
- export type { GscRowQueryMeta, GscRowQueryResponse } from '@gscdump/contracts'
17
-
18
- export interface UseGscRowQueryOptions {
19
- site: MaybeRefOrGetter<string | null | undefined>
20
- state: MaybeRefOrGetter<BuilderState | null | undefined>
21
- /** Gate the query; when `false` stays idle (useful for lazy tabs). */
22
- enabled?: MaybeRefOrGetter<boolean>
23
- }
24
-
25
- export interface UseGscRowQueryReturn<T> {
26
- rows: ComputedRef<T[]>
27
- meta: ComputedRef<GscRowQueryMeta | null>
28
- loading: Ref<boolean>
29
- error: Ref<Error | null>
30
- refresh: () => Promise<void>
31
- }
32
-
33
- export function useGscRowQuery<T = QueryRow>(
34
- opts: UseGscRowQueryOptions,
35
- ): UseGscRowQueryReturn<T> {
36
- const resource = useGscResource<[string, BuilderState], GscRowQueryResponse<T>>({
37
- keys: [
38
- () => (opts.enabled === undefined || toValue(opts.enabled) ? toValue(opts.site) : null),
39
- // BuilderState is structural; stringify so keys[]-watch picks up shape changes.
40
- () => {
41
- const s = toValue(opts.state)
42
- return s ? JSON.stringify(s) as unknown as BuilderState : null
43
- },
44
- ],
45
- fetcher: site => useGscAnalyticsClient().queryRows<T>(site, toValue(opts.state) as BuilderState),
46
- isEmpty: r => r.rows.length === 0,
47
- })
48
-
49
- return {
50
- rows: computed(() => resource.data.value?.rows ?? []),
51
- meta: computed(() => resource.data.value?.meta ?? null),
52
- loading: resource.loading as unknown as Ref<boolean>,
53
- error: resource.error,
54
- refresh: resource.refresh,
55
- }
56
- }
@@ -1,150 +0,0 @@
1
- /**
2
- * CONTRACT — consolidated analyzer composable public API (Wave-2, frozen).
3
- *
4
- * The single composable that replaces BOTH `useGscAnalyzer.ts` (pkg) and
5
- * `useBrowserAnalyzer.ts` (app). One OPFS-backed, attach-once, browser-
6
- * eligibility-aware analyzer.
7
- *
8
- * This file is the INTERFACE ONLY — the Wave-2 implementation agent writes
9
- * `useGscSnapshotAnalyzer.ts` against these types, then deletes the two old
10
- * composables. Naming follows the package convention (`useGsc*`).
11
- *
12
- * Behaviour the types lock in:
13
- * - ATTACH-ONCE — DuckDB-WASM boots once; OPFS-cached parquet attaches once
14
- * per `(site, searchType)`. Filter / date-range changes within the attached
15
- * span re-query, they do NOT re-attach.
16
- * - OPFS-backed — files resolved via the file-resolution endpoint are cached
17
- * in OPFS (content-hash verified); steady-state queries are zero-network.
18
- * - RESULT LRU — identical archetype queries replay from an in-memory LRU.
19
- * - BROWSER-ELIGIBILITY AWARE — when the file-resolution endpoint returns a
20
- * server-tail directive for a table, `query()` for that table transparently
21
- * routes server-side; the consumer does not branch.
22
- *
23
- * TYPES ONLY — no composable body.
24
- */
25
-
26
- import type { GscSearchType } from '@gscdump/contracts'
27
- import type {
28
- ArchetypeQuery,
29
- ArchetypeResult,
30
- ArchetypeResultRow,
31
- } from '@gscdump/sdk'
32
- import type { MaybeRefOrGetter, Ref } from 'vue'
33
-
34
- /** Date window the analyzer is attached over. */
35
- export interface AnalyzerRange {
36
- start: string
37
- end: string
38
- }
39
-
40
- /** Boot / attach lifecycle phase, surfaced for the progress UI. */
41
- export type AnalyzerPhase
42
- = | 'idle'
43
- | 'resolving' // calling the file-resolution endpoint
44
- | 'booting' // DuckDB-WASM boot
45
- | 'downloading' // fetching parquet into OPFS
46
- | 'attaching' // registering OPFS files as DuckDB views
47
- | 'ready'
48
- | 'error'
49
-
50
- /** Progressive-load progress, drives `<GscBootProgress>`. */
51
- export interface AnalyzerProgress {
52
- phase: AnalyzerPhase
53
- /** Files fetched into OPFS so far. */
54
- filesReady: number
55
- /** Total files to fetch for the current attach. */
56
- filesTotal: number
57
- /** Bytes fetched into OPFS so far. */
58
- bytesReady: number
59
- bytesTotal: number
60
- startedAt?: number
61
- endedAt?: number
62
- error?: string
63
- }
64
-
65
- /** OPFS storage health, surfaced so the UI can warn before eviction. */
66
- export interface AnalyzerStorageState {
67
- /** `navigator.storage.persist()` result. `false` => eviction-eligible. */
68
- persisted: boolean
69
- /** Estimated bytes used by this origin's OPFS. */
70
- usageBytes?: number
71
- /** Estimated quota. */
72
- quotaBytes?: number
73
- /** True after a `QuotaExceededError` forced a degraded (server-tail) path. */
74
- degraded: boolean
75
- }
76
-
77
- /**
78
- * Per-table routing the analyzer resolved. `'browser'` tables answer locally
79
- * from OPFS; `'server'` tables route to the server tail. Consumers may read
80
- * this to show "deep history served from the cloud" UX.
81
- */
82
- export type AnalyzerTableRouting = Record<string, 'browser' | 'server'>
83
-
84
- /** Options for `useGscSnapshotAnalyzer`. */
85
- export interface GscSnapshotAnalyzerOptions {
86
- /** Max entries in the result LRU. Default implementation-defined. */
87
- resultCacheSize?: number
88
- }
89
-
90
- /**
91
- * The consolidated analyzer instance. Refs are reactive over the currently
92
- * bound `(site, searchType, range)` — switching any of them rebinds and the
93
- * refs track the new attach with no manual mirroring.
94
- */
95
- export interface GscSnapshotAnalyzer {
96
- /** True once the analyzer can serve `query()` (browser attached OR server-tail ready). */
97
- ready: Ref<boolean>
98
- /** True while resolving / booting / attaching. */
99
- initializing: Ref<boolean>
100
- error: Ref<Error | null>
101
- progress: Ref<AnalyzerProgress>
102
- storage: Ref<AnalyzerStorageState>
103
- /** Per-table browser vs server routing for the current attach. */
104
- routing: Ref<AnalyzerTableRouting>
105
- /** Snapshot version of the currently-attached file set (re-attach trigger). */
106
- snapshotVersion: Ref<string | undefined>
107
- /** The site currently bound. */
108
- currentSiteId: Ref<string | null>
109
-
110
- /**
111
- * Run a typed archetype query. Routes automatically:
112
- * - browser-eligible tables → DuckDB-WASM over OPFS files.
113
- * - server-tail tables → R2 SQL or server DuckDB per the directive.
114
- * Identical queries replay from the result LRU. Honours `signal`.
115
- */
116
- query: <R extends ArchetypeResultRow = ArchetypeResultRow>(
117
- q: ArchetypeQuery,
118
- opts?: { signal?: AbortSignal },
119
- ) => Promise<ArchetypeResult<R>>
120
-
121
- /**
122
- * Re-probe the file-resolution endpoint; if `snapshotVersion` changed,
123
- * detach stale views and re-attach the fresher parquet in place (the
124
- * DuckDB-WASM runtime stays alive). Resolves true if it re-attached.
125
- * No-op for fully server-tail-routed sites.
126
- */
127
- refresh: () => Promise<boolean>
128
-
129
- /** Drop the result LRU without detaching. Cheap cache bust. */
130
- clearCache: () => void
131
-
132
- /** Detach views, close the runtime, release OPFS handles. Idempotent. */
133
- dispose: () => Promise<void>
134
- }
135
-
136
- /**
137
- * Get (or create) the consolidated analyzer for a site. Per-`(site,
138
- * searchType, range)` cached + refcounted across the app, so panels on the
139
- * same site share one DuckDB-WASM boot and one OPFS attach.
140
- *
141
- * Replaces `useGscAnalyzer` (pkg) and `useBrowserAnalyzer` /
142
- * `provideBrowserAnalyzer` / `useSharedBrowserAnalyzer` (app) — those are
143
- * deleted once consumers migrate.
144
- */
145
- export type UseGscSnapshotAnalyzer = (
146
- siteId: MaybeRefOrGetter<string | null | undefined>,
147
- searchType?: MaybeRefOrGetter<GscSearchType | undefined>,
148
- range?: MaybeRefOrGetter<AnalyzerRange | null | undefined>,
149
- options?: GscSnapshotAnalyzerOptions,
150
- ) => GscSnapshotAnalyzer
@@ -1,87 +0,0 @@
1
- /**
2
- * Pure routing + caching helpers for `useGscSnapshotAnalyzer`.
3
- *
4
- * Extracted into a standalone, Nuxt-free module so the routing decision and
5
- * the result LRU are unit-testable without a Nuxt runtime (the composable file
6
- * itself depends on Nuxt auto-imports).
7
- */
8
-
9
- import type { ArchetypeQuery } from '@gscdump/sdk'
10
- import type { AnalyzerTableRouting } from './useGscSnapshotAnalyzer.contract'
11
-
12
- /**
13
- * Decide where an archetype query runs given the analyzer's per-table routing.
14
- *
15
- * - `aux-cloud-only` is always `cloud` (not an Iceberg query).
16
- * - Otherwise the fact-table the archetype reads decides: a table the resolver
17
- * marked `browser` runs locally; `server` (or degraded by a quota error)
18
- * routes to the server tail. An unknown table fails safe to `server`.
19
- */
20
- export function routeArchetype(
21
- query: ArchetypeQuery,
22
- routing: AnalyzerTableRouting,
23
- tableOf: (q: ArchetypeQuery) => string | null,
24
- ): 'browser' | 'server' | 'cloud' {
25
- if (query.archetype === 'aux-cloud-only')
26
- return 'cloud'
27
- const table = tableOf(query)
28
- if (!table)
29
- return 'cloud'
30
- return routing[table] === 'browser' ? 'browser' : 'server'
31
- }
32
-
33
- /**
34
- * Stable hash of an archetype query for the result LRU. Archetype inputs are
35
- * flat + declarative; keys are sorted so insertion order can't perturb the key.
36
- */
37
- export function archetypeQueryHash(query: ArchetypeQuery): string {
38
- return JSON.stringify(query, Object.keys(query as unknown as Record<string, unknown>).sort())
39
- }
40
-
41
- /**
42
- * Compose the LRU cache key. Keyed by `snapshotVersion` so a fresh compaction
43
- * (new snapshot) invalidates every cached result without an explicit bust.
44
- */
45
- export function resultCacheKey(snapshotVersion: string | undefined, query: ArchetypeQuery): string {
46
- return `${snapshotVersion ?? 'none'}::${archetypeQueryHash(query)}`
47
- }
48
-
49
- export interface ResultLru<V> {
50
- get: (key: string) => V | undefined
51
- set: (key: string, value: V) => void
52
- clear: () => void
53
- readonly size: number
54
- }
55
-
56
- /** Minimal insertion-ordered LRU. Re-inserts on `get` to mark recency. */
57
- export function createResultLru<V>(capacity: number): ResultLru<V> {
58
- const map = new Map<string, V>()
59
- const cap = Math.max(1, Math.floor(capacity))
60
- return {
61
- get(key) {
62
- if (!map.has(key))
63
- return undefined
64
- const value = map.get(key)!
65
- map.delete(key)
66
- map.set(key, value)
67
- return value
68
- },
69
- set(key, value) {
70
- if (map.has(key))
71
- map.delete(key)
72
- map.set(key, value)
73
- while (map.size > cap) {
74
- const oldest = map.keys().next().value
75
- if (oldest === undefined)
76
- break
77
- map.delete(oldest)
78
- }
79
- },
80
- clear() {
81
- map.clear()
82
- },
83
- get size() {
84
- return map.size
85
- },
86
- }
87
- }