Uh oh!
There was an error while loading. Please reload this page.
feat(appkit-ui): share in-flight useAnalyticsQuery requests - #524
Open
MarioCadenas wants to merge 5 commits into
Open
feat(appkit-ui): share in-flight useAnalyticsQuery requests#524MarioCadenas wants to merge 5 commits into
MarioCadenas wants to merge 5 commits into
Conversation
Contributor
📦 Bundle size reportCompared against
|
| dist | raw | gzip |
|---|---|---|
| JS (runtime) | 869 KB | 303 KB |
| Type declarations | 315 KB | 109 KB |
| Source maps | 1.7 MB (+159 B) | 566 KB (+143 B) |
| Other | 11 KB | 3.7 KB |
| Total | 2.9 MB (+159 B) | 982 KB (+143 B) |
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 | 457 B | 49 KB | external | 143 KB |
./type-generator | 21 KB | 0 B | 21 KB | external | 61 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 | 434 B |
./beta | client-options.js | initial | 220 B |
./beta | supervisor-api.js | lazy | 192 B |
./beta | databricks.js | lazy | 142 B |
./beta | index.js | lazy | 123 B |
./type-generator | index.js | initial | 21 KB |
@databricks/appkit-ui
npm tarball (packed): 348 KB (+5.7 KB) — gzipped download (dist + bin; excludes release-only docs/NOTICE).
| dist | raw | gzip |
|---|---|---|
| JS (runtime) | 394 KB (+4.0 KB) | 132 KB (+1.7 KB) |
| Type declarations | 228 KB (+341 B) | 83 KB (+152 B) |
| Source maps | 764 KB (+11 KB) | 252 KB (+4.4 KB) |
| CSS | 16 KB (-462 B) | 3.2 KB (-90 B) |
| Total | 1.4 MB (+15 KB) | 471 KB (+6.2 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 (+578 B) | 49 KB | 481 KB (+578 B) | 1.3 MB (+18 B) | 177 KB (+1.3 KB) |
./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 |
Contributor
🤖 AppKit PR bot🔬 Run evalsStart an eval for this PR from the evals-monitor app: Go to Evals Monitor → 📦 Try this PR's app templateScaffolds a new app from this PR's SDK build. Run it in any folder (requires the GitHub CLI — gh run download 32270171607 -R databricks/appkit -n appkit-template-0.61.1-pr.beb493c-in-flight-requests-524 -D appkit-pr-524 \
&& unzip -o "appkit-pr-524/appkit-template-0.61.1-pr.beb493c-in-flight-requests-524.zip" -d "appkit-pr-524" \
&& databricks apps init --template "appkit-pr-524"The template pins |
MarioCadenasforce-pushed
the
in-flight-requests
branch
2 times, most recently
from
August 13, 2026 10:55
d2c6b27 to
70f200eCompareMarioCadenas
marked this pull request as ready for review
August 13, 2026 10:57
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>
Split the analytics-query dedup store into a reusable core so useMetricView
can adopt the same mechanism (follow-up) instead of hand-rolling its own.
- New request-store.ts: createRequestStore<S>(idle) owns the lifecycle only —
keyed coalescing, refcount, deferred teardown, subscribe/getSnapshot. Runs
are transport-agnostic; controls surface is { signal, abort, patch }.
- analytics-request-store.ts becomes an instance: runAnalyticsRequest wires
the SSE/Arrow transport onto controls.patch. Public API (retain/start/
subscribe/getSnapshot/resetAnalyticsRequestStore) unchanged, so the hook and
its tests are untouched.
- Factory-contract tests in __tests__/request-store.test.ts.
Signed-off-by: MarioCadenas <MarioCadenas@users.noreply.github.com>Signed-off-by: MarioCadenas <MarioCadenas@users.noreply.github.com>
MarioCadenasforce-pushed
the
in-flight-requests
branch
from
August 19, 2026 15:27
70f200e to
3650c1dCompare
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for freeto join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
What
useAnalyticsQuerydeduplicates 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
request-store.ts— a generic keyed request store,createRequestStore<S>(idle). It owns only the dedup lifecycle: keyed coalescing, refcount, deferred teardown, and subscribe/notify viauseSyncExternalStore. Transport-agnostic — the caller supplies arun(controls)and reports state throughcontrols.patch.analytics-request-store.tsis now an instance of that factory.runAnalyticsRequestwires the transport (SSE forJSON_ARRAY, direct Arrow fetch forARROW_STREAM) ontocontrols.patch, and fans both the final result and mid-flightwarehouse_statusupdates out to every subscriber. Its public API (retain/start/subscribe/getSnapshot) is unchanged, so the hook and its tests didn't move.use-analytics-query.tsis a thinuseSyncExternalStoresubscriber. Cache key =urlSuffix + serialized({parameters, format}). Warehouse-status mirroring intoResourceStatusProviderlives in a hook effect.useChartDataand all charts route throughuseAnalyticsQuery, so they inherit dedup for free.UseAnalyticsQueryResultis unchanged — non-breaking.Follow-up:
useMetricViewshould adopt the same factoryrequest-store.tswas extracted as a generic factory precisely so the second analytics hook can share the mechanism.useMetricViewcurrently hand-rolls its own weaker lifecycle (effectLease+queueMicrotask+activeRequestKeyRef) that only dedups a StrictMode remount within a single instance — two separate components with identical args still fire two requests.A follow-up PR should migrate
useMetricViewontocreateRequestStore, deleting that bespoke lifecycle and gaining cross-instance dedup for free. It needs two small additions to the factory when it lands: acurrent()control (for its shape-based "keep stale rows during a same-shape revalidation" policy) and ametadatafield in its own snapshot type. Kept out of this PR to avoid rewriting a freshly-shipped hook + its ~40 tests here.Showcase
New
/query-dedupplayground route (Data → "Query Dedup"). It wrapswindow.fetchwhile 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-uiunit suite: 548 passed / 26 files — includes the newrequest-storefactory-contract tests, theuseAnalyticsQuerydedup + store-lifecycle tests, and the warehouse-status integration tests (test files reset the singleton store between cases).typecheck,knip, andoxlint/oxfmt(post-chore: migrate from Biome to oxlint + oxfmt #538 toolchain): clean.