Skip to content

feat(analytics): internal refetch + uncached request mode (poll 1/4) - #584

Open
atilafassina wants to merge 3 commits into
mainfrom
poll-1-refetch
Open

atilafassina wants to merge 3 commits into
mainfrom
poll-1-refetch

Conversation

@atilafassina

Copy link
Copy Markdown
Contributor

Stacked PR 1 of 4 — analytics query polling (useAnalyticsQuery({ poll })).
Base: main. This is the bottom of the stack; review/merge first.

What

Surface the analytics request store's existing settled-key re-run as an internal refetch(cacheKey), and add a force-uncached execution mode so a re-run with unchanged params genuinely re-hits the warehouse.

  • analytics-request-store.ts: internal refetch(cacheKey) marks the key for uncached execution (per-run, consumed once) then calls the generic store.start(cacheKey).
  • Uncached path adds a skipCache: true field to IAnalyticsQueryRequest; the analytics route honors it to bypass its TTL cache (works for JSON_ARRAY and ARROW_STREAM). Backward-compatible (optional, defaults false).

Dormant

No public API, no scheduler. Consumed only by store unit tests. Nothing observable changes yet.

Stack

  1. ← this PR internal refetch + uncached mode
  2. internal usePoll scheduler
  3. useAnalyticsQuery({ poll }) binding + public types (behavior-adding)
  4. docs

Tests

Full suite green; store unit tests assert deferred-start + refetch fresh settle on unchanged params, and skipCache on the outgoing request.

This pull request and its description were written by Isaac.

Surface the request store's existing settled-key re-run as an internal
refetch(cacheKey) on the analytics request layer, and add a force-uncached
execution mode: a per-run skipCache signal on the query payload that the
analytics route honors to bypass its TTL cache. Lands dormant — no public
API, no scheduler; consumed only by store unit tests. Foundation for the
usePoll scheduler (phase 2) and the useAnalyticsQuery({ poll }) binding
(phase 3).

xavier loop: iteration 1 (phase 1/4)

Co-authored-by: Isaac <no-reply@databricks.com>
Signed-off-by: Atila Fassina <atila@fassina.eu>
@github-actions

github-actions Bot commented Sep 14, 2026

Copy link
Copy Markdown
Contributor

📦 Bundle size report

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

@databricks/appkit

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

dist raw gzip
JS (runtime) 1.2 MB (+20 KB) 421 KB (+8.3 KB)
Type declarations 434 KB (+7.1 KB) 157 KB (+3.4 KB)
Source maps 2.3 MB (+44 KB) 790 KB (+17 KB)
Other 11 KB 3.7 KB
Total 3.9 MB (+71 KB) 1.3 MB (+29 KB)
Per-entry composition (own code — deps external (as shipped))
Entry Initial (gz) Lazy (gz) Total (gz) node_modules (min) Own code (min)
. 96 KB (+250 B) 2.5 KB 98 KB (+250 B) external 313 KB (+556 B)
./beta 93 KB (+21 B) 457 B 93 KB (+21 B) external 280 KB (+83 B)
./testing 38 KB (+21 KB) 30 KB (+30 KB) 68 KB (+51 KB) external 199 KB (+147 KB)
./tsdown 520 B 0 B 520 B external 813 B
./type-generator 23 KB 0 B 23 KB external 65 KB

Chunks:

Entry Chunk Load Size (gz)
. index.js initial 92 KB
. utils.js initial 4.0 KB
. remote-tunnel-manager.js lazy 2.5 KB
./beta beta.js initial 76 KB
./beta stream-manager.js initial 5.8 KB
./beta wide-event-emitter.js initial 3.2 KB
./beta databricks.js initial 3.2 KB
./beta configuration.js initial 2.3 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
./testing manifest.js initial 26 KB
./testing index.js initial 9.5 KB
./testing wide-event-emitter.js initial 2.9 KB
./testing index.js lazy 26 KB
./testing remote-tunnel-manager.js lazy 2.5 KB
./testing utils.js lazy 1.2 KB
./tsdown index.js initial 520 B
./type-generator index.js initial 23 KB

@databricks/appkit-ui

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

dist raw gzip
JS (runtime) 395 KB (+575 B) 133 KB (+205 B)
Type declarations 229 KB 84 KB
Source maps 769 KB (+2.3 KB) 254 KB (+803 B)
CSS 16 KB 3.2 KB
Total 1.4 MB (+2.8 KB) 474 KB (+1008 B)
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 433 KB (+55 B) 49 KB 481 KB (+55 B) 1.3 MB (+3 B) 177 KB (+171 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 Sep 14, 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 34876794023 -R databricks/appkit -n appkit-template-0.75.1-pr.84ca15e-poll-1-refetch-584 -D appkit-pr-584 \
  && unzip -o "appkit-pr-584/appkit-template-0.75.1-pr.84ca15e-poll-1-refetch-584.zip" -d "appkit-pr-584" \
  && databricks apps init --template "appkit-pr-584"

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.

atilafassina and others added 2 commits September 14, 2026 17:44
Co-authored-by: Isaac <no-reply@databricks.com>
Signed-off-by: Atila Fassina <atila@fassina.eu>
Rework the poll-1 skipCache mode from a cache *bypass* into a *refresh*.
Previously it disabled the cache (JSON) / skipped getOrExecute (Arrow), which
left the shared entry stale to TTL for other readers and dropped in-flight
single-flight coalescing. Now skipCache invalidates the entry and runs the
normal cached path, so it re-hits the warehouse, writes the fresh result
through to the shared entry, and keeps concurrent-request coalescing.

Also:
- coerce skipCache to a strict boolean so an untrusted body can't enable it
  with a truthy non-boolean (consistent with the format check on the route).
- fix refetch() leaking its one-shot uncached mark when store.start() no-ops
  on an absent entry, and clear the mark set on resetAnalyticsRequestStore.
- add server refresh/write-through + boolean-coercion tests and client
  mark-leak/reset tests.

Co-authored-by: Isaac <no-reply@databricks.com>
Signed-off-by: Atila Fassina <atila@fassina.eu>
@atilafassina
atilafassina marked this pull request as ready for review September 15, 2026 13:30
@atilafassina
atilafassina requested a review from a team as a code owner September 15, 2026 13:30
@atilafassina
atilafassina requested review from pkosiec and a lite review from Copilot September 15, 2026 13:30
const ttl = queryDefaults.cache?.ttl;
return {
query: (q, params, formatParameters, signal) => {
query: async (q, params, formatParameters, signal) => {

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

why is this async now? we are not doing any async operation inside no?

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 Changes recommended

Cache refresh has unresolved one-shot state and in-flight invalidation issues, with missing Arrow route coverage.

Get a fresh assessment by requesting another Copilot review.

Pull request overview

Adds internal analytics refetch support and uncached request propagation for future polling.

Changes:

  • Adds refetch(cacheKey) and skipCache request handling.
  • Supports cache bypassing for JSON and Arrow requests.
  • Expands store and route tests.

Review findings:

  • Moderate (3 votes): skipCache persists across runs instead of being consumed once.
  • Moderate (3 votes): JSON refetch does not invalidate in-flight cache requests.
  • Moderate (3 votes): Arrow refetch has the same in-flight race.
  • Nit (1 vote): Add route-level Arrow cache-refresh coverage.
File summaries
File Summary
packages/appkit/src/plugins/analytics/types.ts Adds the internal skipCache request flag.
packages/appkit/src/plugins/analytics/tests/analytics.test.ts Tests JSON cache refresh and request-flag behavior.
packages/appkit/src/plugins/analytics/analytics.ts Handles cache-bypassed JSON and Arrow requests.
packages/appkit-ui/src/react/hooks/analytics-request-store.ts Implements internal refetch and payload injection.
packages/appkit-ui/src/react/hooks/__tests__/analytics-request-store.test.ts Tests refetch lifecycle and transport behavior.
Review details

Suppressed comments (1)

packages/appkit/src/plugins/analytics/analytics.ts:315

  • The new server tests verify skipCache for the JSON_ARRAY/SSE path, but this forwarding into the separate ARROW_STREAM path is only tested on the client payload. Add a route-level ARROW_STREAM test that primes an INLINE result, sends skipCache: true, and verifies a second warehouse execution plus a subsequent cached fresh result; otherwise the Arrow-specific eviction/repopulation behavior can regress undetected.
        skipCache,
  • Files reviewed: 5/5 changed files
  • Comments generated: 3
  • Review effort level: Lite

💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.

Comment on lines +174 to +176
const shouldSkipCache = options.skipCache || uncachedKeys.has(cacheKey);
if (uncachedKeys.has(cacheKey)) {
uncachedKeys.delete(cacheKey);
Comment on lines +343 to +346
if (skipCache) {
await this.cache.delete(
this.cache.generateKey(cacheConfig.cacheKey, executorKey),
);
Comment on lines +1054 to +1056
if (skipCache) {
await cache.delete(cache.generateKey(arrowKey, executorKey));
}
* Used by polling/refetch operations to force fresh execution.
* @internal
*/
skipCache?: boolean;

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

given that we have this issue #549 should we make this not internal? and also allow the hook in the UI to pass the skip?

Comment on lines +183 to +184
const parsed = JSON.parse(options.payload);
requestPayload = JSON.stringify({ ...parsed, skipCache: true });

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this exploded my mind a bit, don't we have skipCache in the options? why do we need to parse and stringify again?

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.

3 participants