@gscdump/nuxt 0.19.7 → 0.20.0

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 CHANGED
@@ -81,9 +81,10 @@ the data — e.g. nuxtseo.com pro consuming gscdump.com. Two pieces:
81
81
  2. **Hand the layer a per-viewer api key**: register a client plugin that
82
82
  trades the viewer's host session for an api key, then call
83
83
  `setGscFetchHeaders({ 'x-api-key': key })`. The layer's `useGscFetch`
84
- attaches the header to every `/api/__gsc/*` call automatically. The same
85
- header rides parquet GETs in `attachParquetUrlTables` (DuckDB-WASM
86
- browser path), so R2 reads work cross-origin.
84
+ attaches the header to every `/api/__gsc/*` call automatically. Browser
85
+ parquet reads use exact-object URLs minted by the source endpoint; runtime
86
+ preflights can include this header, while DuckDB-WASM range reads are
87
+ authorized by the token embedded in each URL.
87
88
 
88
89
  The `setupGscFetchAuth` helper collapses the boilerplate:
89
90
 
@@ -5,11 +5,12 @@
5
5
  // Use this to render locked/ghost UI upfront without issuing a doomed
6
6
  // analyze() call for each panel.
7
7
  //
8
- // The reactive composable and the non-reactive `loadSourceInfoFor` (used by
9
- // `useGscAnalyzer.createInstance`) share one entry per site via the shared
10
- // site-resource seam, so they collapse to one network read per session.
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
11
 
12
12
  import type { SourceCapabilities } from '@gscdump/analysis'
13
+ import type { SourceInfoOptions } from '@gscdump/contracts'
13
14
  import { acquireSharedEntry, useGscSharedSiteResource } from './_useGscSharedSiteResource'
14
15
  import { useGscAnalyticsClient } from './useGscAnalyticsClient'
15
16
 
@@ -48,6 +49,7 @@ interface SourceInfoEntry {
48
49
  }
49
50
 
50
51
  const SOURCE_INFO_NAMESPACE = 'source-info'
52
+ const DEFAULT_SEARCH_TYPE = 'web'
51
53
 
52
54
  function createEntry(): SourceInfoEntry {
53
55
  return {
@@ -58,12 +60,21 @@ function createEntry(): SourceInfoEntry {
58
60
  }
59
61
  }
60
62
 
61
- function fetchInto(entry: SourceInfoEntry, siteId: string): Promise<void> {
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> {
62
73
  if (entry.pending.value)
63
74
  return entry.pending.value
64
75
  entry.loading.value = true
65
76
  entry.error.value = null
66
- const p = (useGscAnalyticsClient().getSourceInfo(siteId) as Promise<GscAnalyticsSourceInfo>)
77
+ const p = (useGscAnalyticsClient().getSourceInfo(siteId, options) as Promise<GscAnalyticsSourceInfo>)
67
78
  .then((data) => {
68
79
  entry.info.value = data
69
80
  })
@@ -85,11 +96,11 @@ function fetchInto(entry: SourceInfoEntry, siteId: string): Promise<void> {
85
96
  * site-resource seam, so gating UI and the analyzer mode probe collapse to
86
97
  * one network read per site per session.
87
98
  */
88
- export async function loadSourceInfoFor(siteId: string): Promise<GscAnalyticsSourceInfo> {
89
- const entry = acquireSharedEntry(SOURCE_INFO_NAMESPACE, siteId, createEntry)
99
+ export async function loadSourceInfoFor(siteId: string, options?: SourceInfoOptions): Promise<GscAnalyticsSourceInfo> {
100
+ const entry = acquireSharedEntry(SOURCE_INFO_NAMESPACE, sourceInfoKey(siteId, options), createEntry)
90
101
  if (entry.info.value)
91
102
  return entry.info.value
92
- await fetchInto(entry, siteId)
103
+ await fetchInto(entry, siteId, options)
93
104
  if (entry.error.value)
94
105
  throw entry.error.value
95
106
  if (!entry.info.value)
@@ -99,8 +110,13 @@ export async function loadSourceInfoFor(siteId: string): Promise<GscAnalyticsSou
99
110
 
100
111
  export function useGscAnalyticsSourceInfo(
101
112
  siteId: MaybeRefOrGetter<string | null | undefined>,
113
+ options: MaybeRefOrGetter<SourceInfoOptions | null | undefined> = null,
102
114
  ): GscAnalyticsSourceInfoState {
103
- const { bound } = useGscSharedSiteResource<SourceInfoEntry>(SOURCE_INFO_NAMESPACE, siteId, {
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, {
104
120
  factory: () => createEntry(),
105
121
  // No onDispose: source-info is cheap and persistent across the session.
106
122
  })
@@ -113,7 +129,7 @@ export function useGscAnalyticsSourceInfo(
113
129
  if (!id)
114
130
  return
115
131
  if (entry.info.value == null && entry.pending.value == null && entry.error.value == null)
116
- void fetchInto(entry, id)
132
+ void fetchInto(entry, id, toValue(options) ?? undefined)
117
133
  }, { immediate: true })
118
134
  }
119
135
 
@@ -127,7 +143,7 @@ export function useGscAnalyticsSourceInfo(
127
143
  if (!entry || !id)
128
144
  return
129
145
  entry.info.value = null
130
- await fetchInto(entry, id)
146
+ await fetchInto(entry, id, toValue(options) ?? undefined)
131
147
  }
132
148
 
133
149
  function supports(analyzerId: MaybeRefOrGetter<string>): ComputedRef<boolean> {
@@ -21,7 +21,6 @@ import { useGscSharedSiteResource } from './_useGscSharedSiteResource'
21
21
  import { useGscAnalyticsContext } from './useGscAnalytics'
22
22
  import { useGscAnalyticsClient } from './useGscAnalyticsClient'
23
23
  import { useGscAnalyticsConfig } from './useGscAnalyticsConfig'
24
- import { loadSourceInfoFor } from './useGscAnalyticsSourceInfo'
25
24
  import { resolveGscAuthHeaders } from './useGscAuth'
26
25
 
27
26
  export interface GscAnalyzerTimings {
@@ -51,6 +50,58 @@ export interface GscAnalyzerInstance {
51
50
  }
52
51
 
53
52
  const EMPTY_TABLES: readonly string[] = Object.freeze([])
53
+ const DEFAULT_SEARCH_TYPE: NonNullable<AnalysisParams['searchType']> = 'web'
54
+ const DEFAULT_ATTACH_FETCH_CONCURRENCY = 2
55
+ const DEFAULT_ATTACH_MAX_FILES = 32
56
+ const DEFAULT_ATTACH_MAX_BYTES = 16 * 1024 * 1024
57
+
58
+ interface AnalyzerRange {
59
+ start: string
60
+ end: string
61
+ }
62
+
63
+ interface AnalysisSourcesClient {
64
+ getAnalysisSources: (
65
+ siteId: string,
66
+ tables?: string[] | string | { tables?: string[] | string, searchType?: NonNullable<AnalysisParams['searchType']>, start?: string, end?: string },
67
+ options?: { searchType?: NonNullable<AnalysisParams['searchType']>, start?: string, end?: string },
68
+ ) => Promise<AnalysisSourcesResponse>
69
+ getSourceInfo: (
70
+ siteId: string,
71
+ options?: { searchType?: NonNullable<AnalysisParams['searchType']>, start?: string, end?: string },
72
+ ) => Promise<SourceInfoResponse>
73
+ }
74
+
75
+ function normalizeSearchType(searchType: AnalysisParams['searchType']): NonNullable<AnalysisParams['searchType']> {
76
+ return searchType ?? DEFAULT_SEARCH_TYPE
77
+ }
78
+
79
+ function analyzerCacheKey(siteId: string, searchType: NonNullable<AnalysisParams['searchType']>, range: AnalyzerRange | null): string {
80
+ return JSON.stringify([siteId, searchType, range?.start ?? null, range?.end ?? null])
81
+ }
82
+
83
+ function parseAnalyzerCacheKey(key: string): { siteId: string, searchType: NonNullable<AnalysisParams['searchType']>, range: AnalyzerRange | null } {
84
+ const [siteId, searchType, start, end] = JSON.parse(key) as [string, NonNullable<AnalysisParams['searchType']>, string | null, string | null]
85
+ return { siteId, searchType, range: start && end ? { start, end } : null }
86
+ }
87
+
88
+ function normalizeRange(range: AnalyzerRange | null | undefined): AnalyzerRange | null {
89
+ return range?.start && range?.end ? range : null
90
+ }
91
+
92
+ function loadSourceInfo(siteId: string, searchType: NonNullable<AnalysisParams['searchType']>, range: AnalyzerRange | null): Promise<SourceInfoResponse> {
93
+ return (useGscAnalyticsClient() as unknown as AnalysisSourcesClient).getSourceInfo(siteId, {
94
+ searchType,
95
+ ...(range ? { start: range.start, end: range.end } : {}),
96
+ })
97
+ }
98
+
99
+ function loadAnalysisSources(siteId: string, searchType: NonNullable<AnalysisParams['searchType']>, range: AnalyzerRange | null): Promise<AnalysisSourcesResponse> {
100
+ return (useGscAnalyticsClient() as unknown as AnalysisSourcesClient).getAnalysisSources(siteId, undefined, {
101
+ searchType,
102
+ ...(range ? { start: range.start, end: range.end } : {}),
103
+ })
104
+ }
54
105
 
55
106
  /**
56
107
  * Get (or create) an analyzer for a site. Per-site cached across the app so
@@ -59,10 +110,21 @@ const EMPTY_TABLES: readonly string[] = Object.freeze([])
59
110
  * bound cached instance; switching `siteId` rebinds and the computeds track
60
111
  * the new instance with no manual mirroring.
61
112
  */
62
- export function useGscAnalyzer(siteId: MaybeRefOrGetter<string | null | undefined>): GscAnalyzerInstance & { currentSiteId: Ref<string | null> } {
113
+ export function useGscAnalyzer(
114
+ siteId: MaybeRefOrGetter<string | null | undefined>,
115
+ searchType: MaybeRefOrGetter<AnalysisParams['searchType']> = DEFAULT_SEARCH_TYPE,
116
+ range: MaybeRefOrGetter<AnalyzerRange | null | undefined> = null,
117
+ ): GscAnalyzerInstance & { currentSiteId: Ref<string | null> } {
63
118
  const ctx = useGscAnalyticsContext()
64
- const { bound, currentSiteId } = useGscSharedSiteResource<GscAnalyzerInstance>('analyzer', siteId, {
65
- factory: id => createInstance(id, ctx.patchProgress, () => loadSourceInfoFor(id) as Promise<SourceInfoResponse>),
119
+ const cacheKey = computed(() => {
120
+ const id = toValue(siteId)
121
+ return id ? analyzerCacheKey(id, normalizeSearchType(toValue(searchType)), normalizeRange(toValue(range))) : null
122
+ })
123
+ const { bound, currentSiteId: currentCacheKey } = useGscSharedSiteResource<GscAnalyzerInstance>('analyzer', cacheKey, {
124
+ factory: (key) => {
125
+ const parsed = parseAnalyzerCacheKey(key)
126
+ return createInstance(parsed.siteId, parsed.searchType, parsed.range, ctx.patchProgress)
127
+ },
66
128
  onDispose: inst => inst.dispose(),
67
129
  })
68
130
 
@@ -74,6 +136,10 @@ export function useGscAnalyzer(siteId: MaybeRefOrGetter<string | null | undefine
74
136
  const attachedTables = computed(() => bound.value?.attachedTables.value ?? (EMPTY_TABLES as string[])) as unknown as Ref<string[]>
75
137
  const timings = computed(() => bound.value?.timings.value ?? null) as unknown as Ref<GscAnalyzerTimings | null>
76
138
  const manifestVersion = computed(() => bound.value?.manifestVersion.value) as unknown as Ref<string | undefined>
139
+ const currentSiteId = computed(() => {
140
+ const key = currentCacheKey.value
141
+ return key ? parseAnalyzerCacheKey(key).siteId : null
142
+ }) as unknown as Ref<string | null>
77
143
 
78
144
  async function query(sql: string, params?: unknown[]): Promise<QueryResult> {
79
145
  const inst = bound.value
@@ -115,8 +181,9 @@ export function useGscAnalyzer(siteId: MaybeRefOrGetter<string | null | undefine
115
181
 
116
182
  function createInstance(
117
183
  siteId: string,
184
+ searchType: NonNullable<AnalysisParams['searchType']>,
185
+ range: AnalyzerRange | null,
118
186
  patchProgress: (id: string, p: Partial<SiteLoadProgress>) => void,
119
- sourceInfoLoader: () => Promise<SourceInfoResponse>,
120
187
  ): GscAnalyzerInstance {
121
188
  const ready = ref(false)
122
189
  const initializing = ref(true)
@@ -133,6 +200,7 @@ function createInstance(
133
200
  let bootedDb: DuckDBWasmBootResult | null = null
134
201
  let attachedHandle: AttachedTablesHandle | null = null
135
202
  let mode: 'browser-attached' | 'server' = 'server'
203
+ const lifetimeController = new AbortController()
136
204
  const inFlight = new Map<string, Promise<AnalysisResult & { queryMs: number }>>()
137
205
 
138
206
  // Cross-origin: if the host returns relative parquet URLs (`/api/r2-data/…`)
@@ -146,9 +214,11 @@ function createInstance(
146
214
  return `${apiBase.replace(/\/+$/, '')}${url}`
147
215
  }
148
216
 
149
- async function attachFromSources(sources: AnalysisSourcesResponse): Promise<{ attached: number, total: number }> {
217
+ async function attachFromSources(sources: AnalysisSourcesResponse, signal?: AbortSignal): Promise<{ attached: number, total: number }> {
150
218
  if (!bootedDb)
151
219
  throw new Error('useGscAnalyzer: attachFromSources called before DuckDB boot')
220
+ if (sources.canUseBrowser === false)
221
+ throw new Error(`useGscAnalyzer: browser attach unavailable: ${sources.reason ?? sources.fallback ?? 'coverage plan rejected'}`)
152
222
 
153
223
  const tables = Object.entries(sources.tables)
154
224
  .filter(([, urls]) => Array.isArray(urls) && urls.length > 0)
@@ -157,11 +227,10 @@ function createInstance(
157
227
  const total = tables.reduce((n, t) => n + t.urls.length, 0)
158
228
  patch({ stage: 'attach', filesTotal: total, filesAttached: 0 })
159
229
 
160
- // Cross-origin parquet GETs need the host-supplied auth header (same one
161
- // useGscFetch attaches to /api/__gsc/* calls). DuckDB-WASM runs raw fetch
162
- // under the hood, so we pass the header through fetchInit. Cookies aren't
163
- // useful here the parquet origin (gscdump.com) and the host page sit in
164
- // different session realms when the consumer mode is active.
230
+ // URL preflights can use the same host-supplied auth header as /api/__gsc/*.
231
+ // The actual DuckDB range reads are authorized by the exact-key token
232
+ // embedded in each analysis-sources URL, because registerFileURL cannot
233
+ // carry custom fetch headers into DuckDB-WASM's internal HTTP reader.
165
234
  const extraHeaders = resolveGscAuthHeaders()
166
235
  const hasExtra = Object.keys(extraHeaders).length > 0
167
236
  let attached = 0
@@ -174,6 +243,10 @@ function createInstance(
174
243
  fetchInit: hasExtra
175
244
  ? { credentials: 'omit', headers: extraHeaders }
176
245
  : { credentials: 'same-origin' },
246
+ fetchConcurrency: DEFAULT_ATTACH_FETCH_CONCURRENCY,
247
+ maxFiles: DEFAULT_ATTACH_MAX_FILES,
248
+ maxBytes: DEFAULT_ATTACH_MAX_BYTES,
249
+ signal,
177
250
  onFileAttached: () => {
178
251
  attached++
179
252
  patch({ filesAttached: attached })
@@ -193,7 +266,7 @@ function createInstance(
193
266
  patch({ stage: 'manifest', startedAt: Date.now(), filesAttached: 0, filesTotal: 0, error: undefined, endedAt: undefined })
194
267
  // Probe the server-resolved source first. Its kind + attachedTables bit
195
268
  // decides whether we boot DuckDB-WASM (expensive) or proxy to the server.
196
- const info = await sourceInfoLoader()
269
+ const info = await loadSourceInfo(siteId, searchType, range)
197
270
  mode = info.browserAttachEligible ? 'browser-attached' : 'server'
198
271
 
199
272
  if (mode === 'server') {
@@ -224,11 +297,11 @@ function createInstance(
224
297
 
225
298
  patch({ stage: 'manifest' })
226
299
  const t1 = performance.now()
227
- const sources = await useGscAnalyticsClient().getAnalysisSources(siteId) as AnalysisSourcesResponse
300
+ const sources = await loadAnalysisSources(siteId, searchType, range)
228
301
  const manifestMs = performance.now() - t1
229
302
 
230
303
  const t2 = performance.now()
231
- const { total } = await attachFromSources(sources)
304
+ const { total } = await attachFromSources(sources, lifetimeController.signal)
232
305
  const attachMs = performance.now() - t2
233
306
 
234
307
  runtime = createBrowserAnalysisRuntime(bootedDb, { schema: 'main', attachedTables: attachedTables.value })
@@ -259,7 +332,7 @@ function createInstance(
259
332
  }
260
333
 
261
334
  async function runServerAnalyze(params: AnalysisParams, _signal?: AbortSignal): Promise<AnalysisResult & { queryMs: number }> {
262
- const out = await useGscAnalyticsClient().analyze<AnalysisResult & { queryMs?: number }>(siteId, params)
335
+ const out = await useGscAnalyticsClient().analyze<AnalysisResult & { queryMs?: number }>(siteId, { ...params, searchType: params.searchType ?? searchType })
263
336
  return {
264
337
  results: coerceResults(out.results) as AnalysisResult['results'],
265
338
  meta: out.meta as AnalysisResult['meta'],
@@ -268,18 +341,19 @@ function createInstance(
268
341
  }
269
342
 
270
343
  async function analyze(params: AnalysisParams, opts?: { signal?: AbortSignal }): Promise<AnalysisResult & { queryMs: number }> {
271
- const rt = await boot
344
+ const rt = opts?.signal ? await raceSignal(boot, opts.signal) : await boot
272
345
  opts?.signal?.throwIfAborted?.()
273
346
 
274
- const key = JSON.stringify(params)
347
+ const scopedParams = { ...params, searchType: params.searchType ?? searchType }
348
+ const key = JSON.stringify({ manifestVersion: manifestVersion.value, params: scopedParams, searchType, siteId })
275
349
  const existing = inFlight.get(key)
276
350
  if (existing)
277
351
  return opts?.signal ? raceSignal(existing, opts.signal) : existing
278
352
 
279
353
  const p = (async () => {
280
354
  if (!rt)
281
- return runServerAnalyze(params, opts?.signal)
282
- const out = await rt.analyze(params as never, defaultAnalyzerRegistry)
355
+ return runServerAnalyze(scopedParams, opts?.signal)
356
+ const out = await rt.analyze(scopedParams as never, defaultAnalyzerRegistry, { signal: opts?.signal })
283
357
  opts?.signal?.throwIfAborted?.()
284
358
  return {
285
359
  results: coerceResults(out.results) as AnalysisResult['results'],
@@ -287,19 +361,22 @@ function createInstance(
287
361
  queryMs: out.queryMs,
288
362
  }
289
363
  })()
364
+ if (opts?.signal) {
365
+ return raceSignal(p, opts.signal)
366
+ }
290
367
  inFlight.set(key, p)
291
368
  p.finally(() => {
292
369
  if (inFlight.get(key) === p)
293
370
  inFlight.delete(key)
294
371
  })
295
- return opts?.signal ? raceSignal(p, opts.signal) : p
372
+ return p
296
373
  }
297
374
 
298
375
  async function refresh(): Promise<boolean> {
299
376
  await boot
300
377
  if (mode !== 'browser-attached' || !runtime || !bootedDb)
301
378
  return false
302
- const sources = await useGscAnalyticsClient().getAnalysisSources(siteId) as AnalysisSourcesResponse
379
+ const sources = await loadAnalysisSources(siteId, searchType, range)
303
380
  if (!runtime.isStale(sources.manifestVersion))
304
381
  return false
305
382
  // Drop the stale views before swapping in the new partitions. The runtime
@@ -315,6 +392,7 @@ function createInstance(
315
392
  }
316
393
 
317
394
  async function dispose(): Promise<void> {
395
+ lifetimeController.abort()
318
396
  await runtime?.close().catch((e) => {
319
397
  console.error('[analyzer] runtime.close failed', e)
320
398
  })
@@ -132,8 +132,21 @@ async function defaultServerFallback<T>(siteId: string, params: AnalysisParams):
132
132
  return await useGscAnalyticsClient().analyze<T>(siteId, params)
133
133
  }
134
134
 
135
+ const DEFAULT_SEARCH_TYPE: NonNullable<AnalysisParams['searchType']> = 'web'
136
+
137
+ function withDefaultSearchType(params: AnalysisParams): AnalysisParams {
138
+ return { ...params, searchType: params.searchType ?? DEFAULT_SEARCH_TYPE }
139
+ }
140
+
135
141
  export function useGscQuery<T = AnalysisResult>(opts: UseGscQueryOptions<T>): UseGscQueryReturn<T> {
136
- const analyzer = useGscAnalyzer(opts.site)
142
+ const querySearchType = computed(() => toValue(opts.params).searchType ?? DEFAULT_SEARCH_TYPE)
143
+ const queryRange = computed(() => {
144
+ const params = toValue(opts.params)
145
+ return typeof params.startDate === 'string' && typeof params.endDate === 'string'
146
+ ? { start: params.startDate, end: params.endDate }
147
+ : null
148
+ })
149
+ const analyzer = useGscAnalyzer(opts.site, querySearchType, queryRange)
137
150
  const dispatcher = useGscQueryDispatcher()
138
151
 
139
152
  const data = shallowRef<T | null>(null)
@@ -164,7 +177,7 @@ export function useGscQuery<T = AnalysisResult>(opts: UseGscQueryOptions<T>): Us
164
177
  async function runServer(siteId: string): Promise<void> {
165
178
  const t0 = performance.now()
166
179
  const fn = opts.serverFallback ?? defaultServerFallback
167
- const out = await fn(siteId, toValue(opts.params)) as T
180
+ const out = await fn(siteId, withDefaultSearchType(toValue(opts.params))) as T
168
181
  data.value = out
169
182
  engine.value = 'server'
170
183
  elapsedMs.value = performance.now() - t0
@@ -172,7 +185,7 @@ export function useGscQuery<T = AnalysisResult>(opts: UseGscQueryOptions<T>): Us
172
185
  }
173
186
 
174
187
  async function runBrowser(signal: AbortSignal): Promise<void> {
175
- const out = await analyzer.analyze(toValue(opts.params), { signal })
188
+ const out = await analyzer.analyze(withDefaultSearchType(toValue(opts.params)), { signal })
176
189
  signal.throwIfAborted()
177
190
  const shaped = opts.reshape ? opts.reshape(out) : (out as unknown as T)
178
191
  data.value = shaped
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@gscdump/nuxt",
3
3
  "type": "module",
4
- "version": "0.19.7",
4
+ "version": "0.20.0",
5
5
  "description": "Nuxt layer: GSC analytics UI + DuckDB-WASM integration. Frontend-only; server primitives live in @gscdump/analysis, @gscdump/engine-sqlite, @gscdump/cloudflare, and host apps.",
6
6
  "author": {
7
7
  "name": "Harlan Wilton",
@@ -55,12 +55,12 @@
55
55
  "defu": "^6.1.7",
56
56
  "ofetch": "^1.5.1",
57
57
  "tailwindcss": "^4.3.0",
58
- "@gscdump/contracts": "0.19.7",
59
- "@gscdump/engine": "0.19.7",
60
- "@gscdump/engine-duckdb-wasm": "0.19.7",
61
- "@gscdump/sdk": "0.19.7",
62
- "gscdump": "0.19.7",
63
- "@gscdump/analysis": "0.19.7"
58
+ "@gscdump/engine": "0.20.0",
59
+ "@gscdump/engine-duckdb-wasm": "0.20.0",
60
+ "@gscdump/sdk": "0.20.0",
61
+ "gscdump": "0.20.0",
62
+ "@gscdump/analysis": "0.20.0",
63
+ "@gscdump/contracts": "0.20.0"
64
64
  },
65
65
  "devDependencies": {
66
66
  "@nuxt/kit": "^4.4.6",