Skip to content

feat(appkit-ui): share in-flight useAnalyticsQuery requests - #524

Open
MarioCadenas wants to merge 3 commits into
mainfrom
in-flight-requests
Open

feat(appkit-ui): share in-flight useAnalyticsQuery requests#524
MarioCadenas wants to merge 3 commits into
mainfrom
in-flight-requests

Conversation

@MarioCadenas

Copy link
Copy Markdown
Collaborator

What

useAnalyticsQuery deduplicates identical in-flight requests. When multiple components call the hook with the same query key, parameters, format, and dev mode, they now share a single network request instead of each firing its own.

Closes #496.

How

  • New analytics-request-store.ts — a module-singleton request store mirroring the existing ResourceStatusStore idiom (keyed Map + subscribe/notify + immutable snapshot via useSyncExternalStore). It owns the transport lifecycle (SSE for JSON_ARRAY, direct Arrow fetch for ARROW_STREAM) and fans both the final result and mid-flight warehouse_status updates out to every subscriber.
  • use-analytics-query.ts becomes a thin useSyncExternalStore subscriber. Cache key = urlSuffix + serialized({parameters, format}). Warehouse-status mirroring into ResourceStatusProvider moved from the transport into a hook effect.
  • Lifecycle: a keyed entry lives as long as it has subscribers. Teardown is deferred one tick after the last unsubscribe, so a React StrictMode unmount→remount (or fast route swap) reuses the in-flight request instead of aborting and refetching. Late subscribers read the current snapshot immediately (including an already-resolved result). Dedup-only — no cross-lifecycle result cache.

useChartData and all charts route through useAnalyticsQuery, so they inherit dedup for free. UseAnalyticsQueryResult is unchanged — non-breaking.

Showcase

New /query-dedup playground route (Data → "Query Dedup"). It wraps window.fetch while mounted to count analytics POSTs in-page, so you can watch "N components mounted → 1 network request fired" without the DevTools Network tab. Buttons mount more panels (count stays 1) and give the last panel a different key (count ticks to 2, proving distinct keys still fan out).

Testing

  • appkit-ui suite: 371 passed / 18 files, including new dedup + store-lifecycle tests and the warehouse-status integration tests (both test files reset the singleton store between cases).
  • appkit-ui typecheck: clean. dev-playground client tsc --noEmit: clean.
  • biome check and knip: clean.

Not yet run end-to-end against a live warehouse (no creds in this environment); the dedup counter is exercised by the request-store tests regardless of query success.

@github-actions

github-actions Bot commented Aug 7, 2026

Copy link
Copy Markdown
Contributor

📦 Bundle size report

Compared against bundle-size-baseline.json (main).

@databricks/appkit

npm tarball (packed): 840 KB — gzipped download (dist + bin; excludes release-only docs/NOTICE).

dist raw gzip
JS (runtime) 869 KB 303 KB
Type declarations 314 KB 109 KB
Source maps 1.7 MB 566 KB
Other 11 KB 3.7 KB
Total 2.9 MB 981 KB
Per-entry composition (own code — deps external (as shipped))
Entry Initial (gz) Lazy (gz) Total (gz) node_modules (min) Own code (min)
. 88 KB 2.5 KB 91 KB external 288 KB
./beta 49 KB 458 B 49 KB external 143 KB
./type-generator 21 KB 0 B 21 KB external 60 KB

Chunks:

Entry Chunk Load Size (gz)
. index.js initial 84 KB
. utils.js initial 4.0 KB
. remote-tunnel-manager.js lazy 2.5 KB
./beta beta.js initial 33 KB
./beta stream-manager.js initial 5.8 KB
./beta wide-event-emitter.js initial 3.2 KB
./beta databricks.js initial 3.0 KB
./beta configuration.js initial 2.1 KB
./beta service-context.js initial 1.3 KB
./beta client.js initial 431 B
./beta client-options.js initial 219 B
./beta supervisor-api.js lazy 194 B
./beta databricks.js lazy 142 B
./beta index.js lazy 122 B
./type-generator index.js initial 21 KB

@databricks/appkit-ui

npm tarball (packed): 346 KB (+4.5 KB) — gzipped download (dist + bin; excludes release-only docs/NOTICE).

dist raw gzip
JS (runtime) 393 KB (+3.3 KB) 132 KB (+1.4 KB)
Type declarations 228 KB (+341 B) 83 KB (+152 B)
Source maps 761 KB (+8.4 KB) 251 KB (+3.4 KB)
CSS 16 KB 3.3 KB
Total 1.4 MB (+12 KB) 469 KB (+4.9 KB)
Per-entry composition (consumer bundle — deps bundled, peerDeps external)
Entry Initial (gz) Lazy (gz) Total (gz) node_modules (min) Own code (min)
./js 5.3 KB 49 KB 55 KB 208 KB 14 KB
./js/beta 20 B 0 B 20 B 0 B 0 B
./react 432 KB (+413 B) 49 KB 481 KB (+413 B) 1.3 MB (+27 B) 176 KB (+1002 B)
./react/beta 1.0 KB 0 B 1.0 KB 0 B 1.9 KB

Chunks:

Entry Chunk Load Size (gz)
./js index.js initial 5.2 KB
./js chunk initial 120 B
./js apache-arrow lazy 49 KB
./js/beta beta.js initial 20 B
./react index.js initial 430 KB
./react tslib initial 2.1 KB
./react apache-arrow lazy 49 KB
./react/beta beta.js initial 1.0 KB

@github-actions

github-actions Bot commented Aug 7, 2026

Copy link
Copy Markdown
Contributor

🤖 AppKit PR bot

🔬 Run evals

Start an eval for this PR from the evals-monitor app: Go to Evals Monitor →

📦 Try this PR's app template

Scaffolds a new app from this PR's SDK build. Run it in any folder (requires the GitHub CLI — gh auth login — and the Databricks CLI):

gh run download 31693281308 -R databricks/appkit -n appkit-template-0.60.0-pr.2fb3ce8-in-flight-requests-524 -D appkit-pr-524 \
  && unzip -o "appkit-pr-524/appkit-template-0.60.0-pr.2fb3ce8-in-flight-requests-524.zip" -d "appkit-pr-524" \
  && databricks apps init --template "appkit-pr-524"

The template pins @databricks/appkit and @databricks/appkit-ui to tarballs built from this branch, so the scaffolded app runs against this PR's code.

Identical analytics requests (same query key, parameters, format, and dev
mode) now share a single in-flight network request instead of one per hook
instance. A module-singleton request store (mirroring the ResourceStatusStore
idiom) owns the transport; useAnalyticsQuery becomes a useSyncExternalStore
subscriber. Late subscribers read the current snapshot; the request is torn
down a tick after the last subscriber unmounts, so a StrictMode
unmount->remount reuses it rather than aborting.

useChartData and all charts inherit the dedup for free. Dedup-only, no result
cache. UseAnalyticsQueryResult is unchanged (non-breaking).

Adds a /query-dedup playground route that counts analytics fetches in-page to
make the behavior observable.

Closes #496

Signed-off-by: MarioCadenas <MarioCadenas@users.noreply.github.com>
…ery dedup

The arrow-analytics and data-visualization integration specs asserted one
network request per chart instance (times a StrictMode x2 multiplier). With
shared in-flight requests, components resolving to the same (queryKey,
parameters, format) signature now share one request, and deferred teardown
means StrictMode remounts reuse it rather than refiring — so the multiplier no
longer applies to request counts.

Update expectations to the deduplicated counts (each key collapses to one
request per distinct resolved format) and drop the now-unused
STRICT_MODE_MULTIPLIER.

Signed-off-by: MarioCadenas <MarioCadenas@users.noreply.github.com>
Quality-only cleanup of the shared-request code, behavior unchanged:

- use-analytics-query: drop the redundant `cacheKey === null` guards and the
  NOOP_SUBSCRIBE constant (a verbatim copy of the one in use-resource-status).
  The store already returns the stable idle snapshot for unused keys, so the
  cache key is computed unconditionally; only the `retain` and error-field
  guards on `payload === null` remain. Un-export EMPTY_SNAPSHOT from the store.
- query-dedup demo route: replace the hand-rolled useSyncExternalStore counter
  with a plain useState (single consumer).
- Trim verbose/duplicated comments across the store, hook, and integration
  specs to their load-bearing facts.

Signed-off-by: MarioCadenas <MarioCadenas@users.noreply.github.com>
@MarioCadenas
MarioCadenas marked this pull request as ready for review August 13, 2026 10:57
@MarioCadenas
MarioCadenas requested a review from a team as a code owner August 13, 2026 10:57
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

useAnalyticsQuery issues duplicate network requests for identical queries across hook instances

1 participant