Skip to content

feat(client): prefetch a list of instances in one wave - #17

Open
ahinoamp wants to merge 1 commit into
Kitware:mainfrom
ahinoamp:feat/prefetch
Open

ahinoamp wants to merge 1 commit into
Kitware:mainfrom
ahinoamp:feat/prefetch

Conversation

@ahinoamp

Copy link
Copy Markdown
Contributor

Replaces #14. #15 already covers that PR's other two commits: the shared definition request, and a provider waiting for its whole graph. This PR keeps only prefetch, rebuilt on the shared client-core.

Problem

A Provider renders its slot only once its own instance is available. When providers are nested, the inner one is not created until the outer one has loaded, so a page of N nested providers loads them one after another. #15 parallelizes loading within one provider's graph, but independent nested providers still form a chain.

Change

  • DataclassCore.prefetch(ids) (client-core, so both clients have it) starts every id loading at once, each over its whole graph through fetchInstanceRecursive. It resolves to [{ id, ready, error }] once all have settled.
    • Every request is shared with the providers, so nothing is fetched twice, and a provider that mounts later finds its instance already loading.
    • It uses allSettled, so one unknown or failing id does not fail the rest.
  • trame-dataclass-prefetch (Vue) and dataclass.Prefetch (Python) wrap it.
    • The slot always renders, and each provider still waits for its own data, so removing the wrapper only changes timing.
    • The widget turns a list of ids into a bound, single-quoted array literal. A tuple stays a binding expression. An id that could escape the quotes is refused, and so is client_type="react", which has no component yet. React code can call prefetch on its manager directly.
with dataclass.Prefetch(instances=[m._id for m in models]):
    with models[0].provide_as("settings"), models[1].provide_as("scene"):
        html.Div("{{ settings.title }} / {{ scene.name }}")

Measured on current main

examples/getting_started/prefetch.py: 20 nested providers, with --serial for the same page without the wrapper. Headless Chromium, median of 5 loads, from navigation to the innermost provider's text. Latency was added with a TCP relay delaying each direction by 20 ms.

without Prefetch with Prefetch
40 ms round trip 1,356 ms 480 ms
local, no added latency 94 ms 59 ms

Tests

  • client-core/tests/prefetch.test.js (4 cases): one wave for all ids; children of a container loaded; an id already loading or loaded not fetched again; an unknown id reported without failing the rest. client-core: 27 passed.
  • tests/test_widgets_prefetch.py (5 cases): list → bound array literal; tuple stays a binding; no ids → no prop; unsafe id refused; React refused.
  • vue-components: builds; 11 passed.

🤖 Generated with Claude Code

A provider renders its slot only once its own instance is available, so
providers nested inside each other load one after another: the inner one
is not created until the outer one has loaded.

DataclassCore.prefetch(ids) starts every id loading at once, each over its
whole graph, and resolves to [{ id, ready, error }] once all have settled.
It reuses fetchInstanceRecursive, so every request is shared with the
providers and nothing is fetched twice. The Vue component
trame-dataclass-prefetch and the dataclass.Prefetch widget wrap it. The
slot always renders; each provider still waits for its own data.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

This branch has not been deployed

No deployments
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.

1 participant