@gscdump/nuxt 0.21.2 → 0.22.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.
- package/README.md +15 -18
- package/app/composables/_useGscQueryDispatcher.ts +5 -4
- package/app/composables/useGscAnalytics.ts +11 -3
- package/app/composables/useGscAnalyzerBatch.ts +2 -2
- package/app/composables/useGscPanelContext.ts +9 -2
- package/app/composables/useGscParquetTable.ts +3 -4
- package/app/composables/useGscQueryDispatcher.ts +14 -122
- package/app/composables/useGscResource.ts +11 -114
- package/app/composables/useGscSiteAnalyzer.ts +500 -0
- package/app/utils/duckdb-wasm.ts +166 -0
- package/nuxt.config.ts +5 -0
- package/package.json +9 -8
- package/types.ts +8 -3
- package/app/composables/useGscEngine.ts +0 -16
- 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
package/README.md
CHANGED
|
@@ -86,31 +86,28 @@ the data — e.g. nuxtseo.com pro consuming gscdump.com. Two pieces:
|
|
|
86
86
|
preflights can include this header, while DuckDB-WASM range reads are
|
|
87
87
|
authorized by the token embedded in each URL.
|
|
88
88
|
|
|
89
|
-
|
|
89
|
+
Wire it from a `'pre'`-enforced client plugin so downstream layer plugins see
|
|
90
|
+
the auth on first read. The layer's `setGscAuth` accepts a value, a `Ref`,
|
|
91
|
+
or a getter — use a getter when credentials may refresh during the session:
|
|
90
92
|
|
|
91
93
|
```ts
|
|
92
94
|
// plugins/00.gscdump-auth.client.ts
|
|
93
|
-
export default
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
flag.value = !!settings?.browserAnalyzerEnabled
|
|
95
|
+
export default defineNuxtPlugin({
|
|
96
|
+
name: 'gscdump-auth',
|
|
97
|
+
enforce: 'pre',
|
|
98
|
+
async setup() {
|
|
99
|
+
const creds = ref<{ apiKey: string, browserAnalyzerEnabled?: boolean } | null>(null)
|
|
100
|
+
creds.value = await $fetch('/api/me/gscdump-credentials').catch(() => null)
|
|
101
|
+
setGscAuth(() => ({
|
|
102
|
+
apiKey: creds.value?.apiKey ?? null,
|
|
103
|
+
browserAnalyzerEnabled: !!creds.value?.browserAnalyzerEnabled,
|
|
104
|
+
}))
|
|
104
105
|
},
|
|
105
106
|
})
|
|
106
107
|
```
|
|
107
108
|
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
where a `useGscQuery` mounts before credentials resolve.
|
|
111
|
-
|
|
112
|
-
`credentialsEndpoint` returns `{ apiKey: string, ... }` by default. Override
|
|
113
|
-
the field name with `tokenField` and the header name with `headerName`.
|
|
109
|
+
`useGscQuery` mounts that race the credential fetch resolve once the
|
|
110
|
+
`'pre'` plugin's `await` settles — Nuxt blocks subsequent plugins on it.
|
|
114
111
|
|
|
115
112
|
Onboarding and lifecycle state stay outside this layer. A consumer app should
|
|
116
113
|
use `@gscdump/sdk` from its host backend to register users/sites, read
|
|
@@ -7,7 +7,8 @@
|
|
|
7
7
|
|
|
8
8
|
import type { _useGscAuthInternal } from './useGscAuth'
|
|
9
9
|
import type { GscQueryDecisionReason, GscQueryEngine } from './useGscQuery'
|
|
10
|
-
|
|
10
|
+
|
|
11
|
+
const ENGINE_STATE_KEY = 'gscdump:engine'
|
|
11
12
|
|
|
12
13
|
type InternalAuthState = ReturnType<typeof _useGscAuthInternal>['value']
|
|
13
14
|
|
|
@@ -26,14 +27,14 @@ export interface GscFallbackEvent {
|
|
|
26
27
|
}
|
|
27
28
|
|
|
28
29
|
export interface PickEngineOpts {
|
|
29
|
-
/** Per-call override. Wins over `
|
|
30
|
+
/** Per-call override. Wins over the `gscdump:engine` useState + `runtimeConfig.public.analytics.defaultEngine`. */
|
|
30
31
|
perCall?: GscQueryEngine
|
|
31
32
|
}
|
|
32
33
|
|
|
33
34
|
export interface GscQueryDispatcher {
|
|
34
35
|
/**
|
|
35
36
|
* Resolve the active engine into a concrete mode + reason. Consults the
|
|
36
|
-
* resolution chain: `opts.perCall` → `
|
|
37
|
+
* resolution chain: `opts.perCall` → `gscdump:engine` useState → runtimeConfig →
|
|
37
38
|
* `'auto'`. Then maps `auto` against `auth.browserAnalyzerEnabled`.
|
|
38
39
|
*/
|
|
39
40
|
pickEngine: (auth: InternalAuthState, opts?: PickEngineOpts) => GscEngineDecision
|
|
@@ -110,7 +111,7 @@ export function createDefaultGscQueryDispatcher(
|
|
|
110
111
|
}
|
|
111
112
|
|
|
112
113
|
function resolveDefaultEngine(): GscQueryEngine {
|
|
113
|
-
const override =
|
|
114
|
+
const override = useState<GscQueryEngine | null>(ENGINE_STATE_KEY, () => null).value
|
|
114
115
|
if (override)
|
|
115
116
|
return override
|
|
116
117
|
const cfg = useRuntimeConfig().public.analytics as { defaultEngine?: GscQueryEngine } | undefined
|
|
@@ -171,14 +171,22 @@ function createContext(): GscAnalyticsContext {
|
|
|
171
171
|
// Lazy-load: defer refreshSites until something actually reads `sites`.
|
|
172
172
|
// Eager kick-off used to race host plugins that set auth via `setGscAuth`;
|
|
173
173
|
// first call would 401 and the rest of the session would silently degrade.
|
|
174
|
-
// Triggering on first read
|
|
175
|
-
//
|
|
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.
|
|
176
177
|
let kickedOff = false
|
|
177
178
|
function maybeKickOff(): void {
|
|
178
179
|
if (kickedOff || !import.meta.client)
|
|
179
180
|
return
|
|
180
181
|
kickedOff = true
|
|
181
|
-
|
|
182
|
+
const start = (): void => {
|
|
183
|
+
void refreshSites()
|
|
184
|
+
}
|
|
185
|
+
const nuxtApp = useNuxtApp()
|
|
186
|
+
if (nuxtApp.isHydrating)
|
|
187
|
+
onNuxtReady(start)
|
|
188
|
+
else
|
|
189
|
+
queueMicrotask(start)
|
|
182
190
|
}
|
|
183
191
|
const lazySites = computed<SiteListItem[] | null>(() => {
|
|
184
192
|
maybeKickOff()
|
|
@@ -16,7 +16,7 @@ export interface GscAnalyzerBatchEntry<TResult> {
|
|
|
16
16
|
}
|
|
17
17
|
|
|
18
18
|
export interface GscAnalyzerBatchRunner {
|
|
19
|
-
analyze: (params: { type: string,
|
|
19
|
+
analyze: (params: { type: string, startDate?: string, endDate?: string }) => Promise<unknown>
|
|
20
20
|
}
|
|
21
21
|
|
|
22
22
|
export interface UseGscAnalyzerBatchOptions {
|
|
@@ -72,7 +72,7 @@ export function useGscAnalyzerBatch<TResult = unknown>(
|
|
|
72
72
|
return
|
|
73
73
|
states.value = { ...states.value, [id]: { status: 'running', result: null, error: null } }
|
|
74
74
|
try {
|
|
75
|
-
const result = await runner.analyze({ type: id,
|
|
75
|
+
const result = await runner.analyze({ type: id, startDate: range.start, endDate: range.end })
|
|
76
76
|
if (token !== myToken)
|
|
77
77
|
return
|
|
78
78
|
states.value = { ...states.value, [id]: { status: 'done', result: result as TResult, error: null } }
|
|
@@ -6,11 +6,18 @@
|
|
|
6
6
|
// `/analyze` provides these once; panels inject. Type-only — runtime values
|
|
7
7
|
// are the InjectionKey Symbols themselves.
|
|
8
8
|
|
|
9
|
+
import type { AnalysisParams, AnalysisResult } from '@gscdump/analysis'
|
|
9
10
|
import type { InjectionKey } from '@vue/runtime-core'
|
|
10
|
-
|
|
11
|
+
|
|
12
|
+
export interface GscPanelRunner {
|
|
13
|
+
/** Positional-form SQL runner — `{ rows, queryMs }`. */
|
|
14
|
+
query: (sql: string, params?: readonly unknown[]) => Promise<{ rows: Record<string, unknown>[], queryMs: number }>
|
|
15
|
+
/** Registry-driven analyzer dispatch over the same per-site DuckDB-WASM runtime. */
|
|
16
|
+
analyze: (params: AnalysisParams, opts?: { signal?: AbortSignal }) => Promise<AnalysisResult & { queryMs: number }>
|
|
17
|
+
}
|
|
11
18
|
|
|
12
19
|
export interface GscPanelRunnerContext {
|
|
13
|
-
runner:
|
|
20
|
+
runner: GscPanelRunner
|
|
14
21
|
ready: Ref<boolean>
|
|
15
22
|
}
|
|
16
23
|
|
|
@@ -1,10 +1,9 @@
|
|
|
1
1
|
// Parquet-attached DuckDB table view: fuzzy-search + sortable metrics +
|
|
2
2
|
// pagination over a single attached parquet (`main.<table>`), date-filtered.
|
|
3
3
|
//
|
|
4
|
-
//
|
|
5
|
-
//
|
|
6
|
-
//
|
|
7
|
-
// `pages` and `keywords` raw tabs); third+ callers are mechanical.
|
|
4
|
+
// Fires SQL against a parquet view in the browser-attached DuckDB-WASM
|
|
5
|
+
// runtime. Two callers today (analyze.vue's `pages` and `keywords` raw
|
|
6
|
+
// tabs); third+ callers are mechanical.
|
|
8
7
|
//
|
|
9
8
|
// Caller passes the `query` fn from `useGscAnalyzer(siteId)`; the composable
|
|
10
9
|
// owns the SQL builder, the debounce, the watcher, and the count+page
|
|
@@ -1,122 +1,14 @@
|
|
|
1
|
-
//
|
|
2
|
-
//
|
|
3
|
-
//
|
|
4
|
-
//
|
|
5
|
-
//
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
type
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
mode: 'browser' | 'server'
|
|
16
|
-
reason: GscQueryDecisionReason
|
|
17
|
-
/** The resolved intent before the auth/optin mapping — useful for callers that branch on "did the user ask for auto?". */
|
|
18
|
-
requested: GscQueryEngine
|
|
19
|
-
detail?: string
|
|
20
|
-
}
|
|
21
|
-
|
|
22
|
-
export interface GscFallbackEvent {
|
|
23
|
-
reason: string
|
|
24
|
-
at: number
|
|
25
|
-
url: string
|
|
26
|
-
}
|
|
27
|
-
|
|
28
|
-
export interface PickEngineOpts {
|
|
29
|
-
/** Per-call override. Wins over `useGscEngine()` + `runtimeConfig.public.analytics.defaultEngine`. */
|
|
30
|
-
perCall?: GscQueryEngine
|
|
31
|
-
}
|
|
32
|
-
|
|
33
|
-
export interface GscQueryDispatcher {
|
|
34
|
-
/**
|
|
35
|
-
* Resolve the active engine into a concrete mode + reason. Consults the
|
|
36
|
-
* resolution chain: `opts.perCall` → `useGscEngine()` → runtimeConfig →
|
|
37
|
-
* `'auto'`. Then maps `auto` against `auth.browserAnalyzerEnabled`.
|
|
38
|
-
*/
|
|
39
|
-
pickEngine: (auth: InternalAuthState, opts?: PickEngineOpts) => GscEngineDecision
|
|
40
|
-
/** Called once per auto-fallback. Default impl is a no-op. */
|
|
41
|
-
reportFallback: (event: GscFallbackEvent) => void
|
|
42
|
-
}
|
|
43
|
-
|
|
44
|
-
export interface CreateDefaultDispatcherOpts {
|
|
45
|
-
/** When set, fallbacks beacon to this URL with batched payloads. */
|
|
46
|
-
telemetryEndpoint?: string
|
|
47
|
-
}
|
|
48
|
-
|
|
49
|
-
const FLUSH_DELAY_MS = 5000
|
|
50
|
-
|
|
51
|
-
function createReporter(endpoint: string): (event: GscFallbackEvent) => void {
|
|
52
|
-
const buffer: GscFallbackEvent[] = []
|
|
53
|
-
let flushScheduled = false
|
|
54
|
-
|
|
55
|
-
function flush(): void {
|
|
56
|
-
flushScheduled = false
|
|
57
|
-
if (buffer.length === 0)
|
|
58
|
-
return
|
|
59
|
-
const events = buffer.splice(0)
|
|
60
|
-
const body = JSON.stringify({ events })
|
|
61
|
-
if (typeof navigator !== 'undefined' && typeof navigator.sendBeacon === 'function') {
|
|
62
|
-
navigator.sendBeacon(endpoint, new Blob([body], { type: 'application/json' }))
|
|
63
|
-
return
|
|
64
|
-
}
|
|
65
|
-
fetch(endpoint, { method: 'POST', body, headers: { 'content-type': 'application/json' } }).catch(() => {})
|
|
66
|
-
}
|
|
67
|
-
|
|
68
|
-
function scheduleFlush(): void {
|
|
69
|
-
if (flushScheduled || typeof window === 'undefined')
|
|
70
|
-
return
|
|
71
|
-
flushScheduled = true
|
|
72
|
-
setTimeout(flush, FLUSH_DELAY_MS)
|
|
73
|
-
if (typeof document !== 'undefined') {
|
|
74
|
-
const handler = (): void => {
|
|
75
|
-
if (document.visibilityState === 'hidden')
|
|
76
|
-
flush()
|
|
77
|
-
}
|
|
78
|
-
document.addEventListener('visibilitychange', handler, { once: true })
|
|
79
|
-
}
|
|
80
|
-
}
|
|
81
|
-
|
|
82
|
-
return (event: GscFallbackEvent): void => {
|
|
83
|
-
buffer.push(event)
|
|
84
|
-
scheduleFlush()
|
|
85
|
-
}
|
|
86
|
-
}
|
|
87
|
-
|
|
88
|
-
export function createDefaultGscQueryDispatcher(
|
|
89
|
-
opts: CreateDefaultDispatcherOpts = {},
|
|
90
|
-
): GscQueryDispatcher {
|
|
91
|
-
const reportFallback = opts.telemetryEndpoint
|
|
92
|
-
? createReporter(opts.telemetryEndpoint)
|
|
93
|
-
: (): void => {}
|
|
94
|
-
|
|
95
|
-
return {
|
|
96
|
-
pickEngine(auth, opts = {}): GscEngineDecision {
|
|
97
|
-
const requested = opts.perCall ?? resolveDefaultEngine()
|
|
98
|
-
if (requested === 'server')
|
|
99
|
-
return { mode: 'server', reason: 'forced:server', requested }
|
|
100
|
-
if (requested === 'browser')
|
|
101
|
-
return { mode: 'browser', reason: 'forced:browser', requested }
|
|
102
|
-
// 'auto': when the host has wired setGscAuth, derive from the per-user
|
|
103
|
-
// browserAnalyzerEnabled flag. When unwired, preserve legacy 'auto' = browser.
|
|
104
|
-
if (auth._initialized && !auth.browserAnalyzerEnabled)
|
|
105
|
-
return { mode: 'server', reason: 'optin:off', requested }
|
|
106
|
-
return { mode: 'browser', reason: 'auto:browser', requested }
|
|
107
|
-
},
|
|
108
|
-
reportFallback,
|
|
109
|
-
}
|
|
110
|
-
}
|
|
111
|
-
|
|
112
|
-
function resolveDefaultEngine(): GscQueryEngine {
|
|
113
|
-
const override = useGscEngine().value
|
|
114
|
-
if (override)
|
|
115
|
-
return override
|
|
116
|
-
const cfg = useRuntimeConfig().public.analytics as { defaultEngine?: GscQueryEngine } | undefined
|
|
117
|
-
return cfg?.defaultEngine ?? 'auto'
|
|
118
|
-
}
|
|
119
|
-
|
|
120
|
-
export function useGscQueryDispatcher(): GscQueryDispatcher {
|
|
121
|
-
return useNuxtApp().$gscQueryDispatcher
|
|
122
|
-
}
|
|
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,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'
|