Skip to content

Collapse retrying into loading with stale-while-revalidate semantics#1677

Draft
mcintyre94 wants to merge 1 commit into
reactive-store/with-signalfrom
reactive-store/preserve-retrying-error
Draft

Collapse retrying into loading with stale-while-revalidate semantics#1677
mcintyre94 wants to merge 1 commit into
reactive-store/with-signalfrom
reactive-store/preserve-retrying-error

Conversation

@mcintyre94
Copy link
Copy Markdown
Member

@mcintyre94 mcintyre94 commented May 20, 2026

Summary of Changes

This PR modifies the states for the stream-store.

The retrying state is removed, and we now use loading for both first connects and subsequent ones. This matcheshow we structure the action store, and minimises states.

Additionally we preserve the last error (in addition to data) in the loading state, to improve SWR. Again this matches a change made to the action store.

@changeset-bot
Copy link
Copy Markdown

changeset-bot Bot commented May 20, 2026

🦋 Changeset detected

Latest commit: 2fd75aa

The changes in this PR will be included in the next version bump.

This PR includes changesets to release 47 packages
Name Type
@solana/subscribable Major
@solana/kit Major
@solana/react Major
@solana/rpc-spec Major
@solana/rpc-subscriptions-channel-websocket Major
@solana/rpc-subscriptions-spec Major
@solana/rpc-subscriptions Major
@solana/accounts Major
@solana/plugin-interfaces Major
@solana/rpc-api Major
@solana/rpc-transport-http Major
@solana/rpc Major
@solana/sysvars Major
@solana/rpc-subscriptions-api Major
@solana/transaction-confirmation Major
@solana/program-client-core Major
@solana/rpc-graphql Major
@solana/addresses Major
@solana/assertions Major
@solana/codecs-core Major
@solana/codecs-data-structures Major
@solana/codecs-numbers Major
@solana/codecs-strings Major
@solana/codecs Major
@solana/compat Major
@solana/errors Major
@solana/fast-stable-stringify Major
@solana/fixed-points Major
@solana/functional Major
@solana/instruction-plans Major
@solana/instructions Major
@solana/keys Major
@solana/nominal-types Major
@solana/offchain-messages Major
@solana/options Major
@solana/plugin-core Major
@solana/programs Major
@solana/promises Major
@solana/rpc-parsed-types Major
@solana/rpc-spec-types Major
@solana/rpc-transformers Major
@solana/rpc-types Major
@solana/signers Major
@solana/transaction-messages Major
@solana/transactions Major
@solana/wallet-account-signer Major
@solana/webcrypto-ed25519-polyfill Major

Not sure what this means? Click here to learn what changesets are.

Click here if you're a maintainer who wants to add another changeset to this PR

@mcintyre94 mcintyre94 changed the title Collapse loading and retrying into a single loading status Collapse retrying into loading with stale-while-revalidate semantics May 20, 2026
@bundlemon
Copy link
Copy Markdown

bundlemon Bot commented May 20, 2026

BundleMon

Files updated (22)
Status Path Size Limits
react/dist/index.native.mjs
4.33KB (+1.24KB +40.01%) -
react/dist/index.browser.mjs
4.33KB (+1.24KB +39.95%) -
react/dist/index.node.mjs
4.33KB (+1.24KB +39.99%) -
errors/dist/index.browser.mjs
20.69KB (+170B +0.81%) -
errors/dist/index.native.mjs
20.69KB (+170B +0.81%) -
errors/dist/index.node.mjs
20.71KB (+170B +0.81%) -
wallet-account-signer/dist/index.native.mjs
17.53KB (+165B +0.93%) -
wallet-account-signer/dist/index.browser.mjs
17.53KB (+164B +0.92%) -
wallet-account-signer/dist/index.node.mjs
17.54KB (+164B +0.92%) -
kit/dist/index.native.mjs
4.13KB (+161B +3.96%) -
kit/dist/index.node.mjs
4.13KB (+161B +3.96%) -
kit/dist/index.browser.mjs
4.13KB (+159B +3.91%) -
@solana/kit production bundle
kit/dist/index.production.min.js
52.42KB (+153B +0.29%) -
subscribable/dist/index.node.mjs
2.82KB (+144B +5.25%) -
subscribable/dist/index.browser.mjs
2.74KB (+142B +5.32%) -
subscribable/dist/index.native.mjs
2.75KB (+142B +5.31%) -
rpc-subscriptions-spec/dist/index.browser.mjs
2.19KB (-13B -0.58%) -
rpc-subscriptions-spec/dist/index.native.mjs
2.19KB (-13B -0.58%) -
rpc-subscriptions-spec/dist/index.node.mjs
2.23KB (-13B -0.56%) -
rpc-spec/dist/index.browser.mjs
898B (-20B -2.18%) -
rpc-spec/dist/index.native.mjs
897B (-21B -2.29%) -
rpc-spec/dist/index.node.mjs
896B (-21B -2.29%) -
Unchanged files (125)
Status Path Size Limits
rpc-graphql/dist/index.browser.mjs
18.82KB -
rpc-graphql/dist/index.native.mjs
18.81KB -
rpc-graphql/dist/index.node.mjs
18.81KB -
transaction-messages/dist/index.browser.mjs
11.32KB -
transaction-messages/dist/index.native.mjs
11.32KB -
transaction-messages/dist/index.node.mjs
11.32KB -
instruction-plans/dist/index.browser.mjs
6.58KB -
instruction-plans/dist/index.native.mjs
6.58KB -
instruction-plans/dist/index.node.mjs
6.58KB -
fixed-points/dist/index.browser.mjs
5.08KB -
fixed-points/dist/index.native.mjs
5.07KB -
fixed-points/dist/index.node.mjs
5.07KB -
codecs-data-structures/dist/index.browser.mjs
5.04KB -
codecs-data-structures/dist/index.native.mjs
5.03KB -
codecs-data-structures/dist/index.node.mjs
5.03KB -
offchain-messages/dist/index.browser.mjs
4.89KB -
offchain-messages/dist/index.native.mjs
4.89KB -
offchain-messages/dist/index.node.mjs
4.89KB -
transactions/dist/index.browser.mjs
4.07KB -
transactions/dist/index.native.mjs
4.07KB -
transactions/dist/index.node.mjs
4.07KB -
codecs-core/dist/index.browser.mjs
3.62KB -
codecs-core/dist/index.native.mjs
3.62KB -
codecs-core/dist/index.node.mjs
3.62KB -
webcrypto-ed25519-polyfill/dist/index.node.mj
s
3.61KB -
webcrypto-ed25519-polyfill/dist/index.browser
.mjs
3.59KB -
webcrypto-ed25519-polyfill/dist/index.native.
mjs
3.57KB -
rpc-subscriptions/dist/index.browser.mjs
3.37KB -
rpc-subscriptions/dist/index.node.mjs
3.34KB -
rpc-subscriptions/dist/index.native.mjs
3.31KB -
signers/dist/index.browser.mjs
3.26KB -
signers/dist/index.native.mjs
3.26KB -
signers/dist/index.node.mjs
3.26KB -
rpc-transformers/dist/index.browser.mjs
3.16KB -
rpc-transformers/dist/index.native.mjs
3.16KB -
rpc-transformers/dist/index.node.mjs
3.16KB -
keys/dist/index.node.mjs
3.06KB -
addresses/dist/index.browser.mjs
2.93KB -
addresses/dist/index.native.mjs
2.92KB -
addresses/dist/index.node.mjs
2.92KB -
keys/dist/index.browser.mjs
2.85KB -
keys/dist/index.native.mjs
2.85KB -
codecs-strings/dist/index.browser.mjs
2.55KB -
codecs-strings/dist/index.node.mjs
2.51KB -
codecs-strings/dist/index.native.mjs
2.47KB -
transaction-confirmation/dist/index.node.mjs
2.42KB -
transaction-confirmation/dist/index.native.mj
s
2.37KB -
sysvars/dist/index.browser.mjs
2.37KB -
sysvars/dist/index.native.mjs
2.37KB -
transaction-confirmation/dist/index.browser.m
js
2.37KB -
sysvars/dist/index.node.mjs
2.37KB -
rpc/dist/index.node.mjs
1.95KB -
codecs-numbers/dist/index.browser.mjs
1.95KB -
codecs-numbers/dist/index.native.mjs
1.95KB -
codecs-numbers/dist/index.node.mjs
1.94KB -
rpc-transport-http/dist/index.browser.mjs
1.91KB -
rpc-transport-http/dist/index.native.mjs
1.9KB -
rpc/dist/index.native.mjs
1.81KB -
rpc-types/dist/index.browser.mjs
1.8KB -
rpc/dist/index.browser.mjs
1.8KB -
rpc-types/dist/index.native.mjs
1.8KB -
rpc-types/dist/index.node.mjs
1.8KB -
rpc-transport-http/dist/index.node.mjs
1.72KB -
rpc-subscriptions-channel-websocket/dist/inde
x.node.mjs
1.33KB -
rpc-subscriptions-channel-websocket/dist/inde
x.native.mjs
1.27KB -
rpc-subscriptions-channel-websocket/dist/inde
x.browser.mjs
1.26KB -
program-client-core/dist/index.browser.mjs
1.21KB -
program-client-core/dist/index.native.mjs
1.21KB -
program-client-core/dist/index.node.mjs
1.21KB -
options/dist/index.browser.mjs
1.18KB -
options/dist/index.native.mjs
1.18KB -
options/dist/index.node.mjs
1.17KB -
accounts/dist/index.browser.mjs
1.17KB -
accounts/dist/index.native.mjs
1.17KB -
accounts/dist/index.node.mjs
1.16KB -
rpc-api/dist/index.browser.mjs
998B -
rpc-api/dist/index.native.mjs
997B -
rpc-api/dist/index.node.mjs
995B -
compat/dist/index.browser.mjs
969B -
compat/dist/index.native.mjs
968B -
compat/dist/index.node.mjs
966B -
rpc-spec-types/dist/index.browser.mjs
962B -
rpc-spec-types/dist/index.native.mjs
961B -
rpc-spec-types/dist/index.node.mjs
959B -
rpc-subscriptions-api/dist/index.native.mjs
870B -
rpc-subscriptions-api/dist/index.node.mjs
869B -
rpc-subscriptions-api/dist/index.browser.mjs
868B -
promises/dist/index.native.mjs
841B -
promises/dist/index.node.mjs
840B -
promises/dist/index.browser.mjs
839B -
plugin-core/dist/index.browser.mjs
820B -
plugin-core/dist/index.native.mjs
819B -
plugin-core/dist/index.node.mjs
817B -
assertions/dist/index.browser.mjs
783B -
instructions/dist/index.browser.mjs
771B -
instructions/dist/index.native.mjs
770B -
instructions/dist/index.node.mjs
768B -
fast-stable-stringify/dist/index.browser.mjs
726B -
fast-stable-stringify/dist/index.native.mjs
725B -
assertions/dist/index.native.mjs
724B -
fast-stable-stringify/dist/index.node.mjs
724B -
assertions/dist/index.node.mjs
723B -
programs/dist/index.browser.mjs
329B -
programs/dist/index.native.mjs
327B -
programs/dist/index.node.mjs
325B -
fs-impl/dist/index.browser.mjs
245B -
event-target-impl/dist/index.node.mjs
230B -
functional/dist/index.browser.mjs
154B -
functional/dist/index.native.mjs
152B -
text-encoding-impl/dist/index.native.mjs
152B -
functional/dist/index.node.mjs
151B -
codecs/dist/index.browser.mjs
145B -
codecs/dist/index.native.mjs
144B -
codecs/dist/index.node.mjs
142B -
event-target-impl/dist/index.browser.mjs
133B -
ws-impl/dist/index.node.mjs
131B -
text-encoding-impl/dist/index.browser.mjs
122B -
fs-impl/dist/index.node.mjs
120B -
text-encoding-impl/dist/index.node.mjs
119B -
ws-impl/dist/index.browser.mjs
113B -
crypto-impl/dist/index.node.mjs
111B -
crypto-impl/dist/index.browser.mjs
109B -
rpc-parsed-types/dist/index.browser.mjs
66B -
rpc-parsed-types/dist/index.native.mjs
65B -
rpc-parsed-types/dist/index.node.mjs
63B -

Total files change +5.63KB +1.07%

Final result: ✅

View report in BundleMon website ➡️


Current branch size history | Target branch size history

@mcintyre94
Copy link
Copy Markdown
Member Author

@trevor-cortex

Copy link
Copy Markdown

@trevor-cortex trevor-cortex left a comment

Choose a reason for hiding this comment

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

Collapses retrying into loading on ReactiveStreamStore, mirroring the action store's running, and widens loading to carry data: T | undefined + error: unknown so the prior outcome is preserved across reconnects (SWR). Reasonable shape change — the implementation in both stores becomes uniformly setState({ data: currentState.data, error: currentState.error, status: 'loading' }) and the existing setState identity guard naturally no-ops the loading → loading re-entry (already covered by the pre-existing 'does not notify subscribers on the loading → loading re-entry' test).

packages/subscribable/README.md is half-migrated and now self-contradictory. The PR only made minimal textual tweaks here, leaving stale retrying references that disagree with the implementation. Three spots:

  1. The ReactiveState<T> snippet in the README still lists a retrying variant (lines 38–42 of the new README). The diff just changed error: undefinederror: unknown on that line; the line should be removed outright, since retrying is no longer part of the type. As-is, the README contradicts the source.
  2. The paragraph just below (line 47) still says "transitions through retrying while preserving the last known value and the last error".
  3. Further down (around line 135), the createReactiveStoreFromDataPublisherFactory "Things to note" bullet wasn't touched at all and still reads: "From idle, the store transitions through loading; from any other status, through retrying while preserving the last known value." Same problem.

Inline comments on the first two. #3 isn't part of the diff so flagging it here.

Things to watch out for / verify:

  • Migration heuristic in the changeset (status === 'loading' && data !== undefined) misses the case where a retry was kicked off from error but no loaded had ever occurred before that error — then data === undefined and the loading is indistinguishable from a first-load from idle. The changeset already acknowledges this loosely ("or just status === 'loading' if you don't need to distinguish"), so this is fine, but worth keeping in mind for downstream callers that genuinely need the first-load-vs-reconnect distinction — the new model doesn't preserve it.
  • Test coverage looks good: kit's error → loading (preserves data + error), subscribable's error → loading and loaded → loading, and the loading → loading no-notify case are all covered. The notify-on-transition tests still assert the right thing (the status discriminator changes, so subscribers still fire).
  • Behavioural diff worth keeping in the back of the head for downstream consumers: previously, a connect() from error cleared error immediately on transition, so any UI gated on error !== undefined would clear at that moment. Now error survives across the transition until the next loaded (or a new error) replaces it. This is the intended SWR semantic and matches the changeset, just want it surfaced for review.

Notes for subsequent reviewers:

  • Pre-existing staleness elsewhere in the README that's outside the scope of this PR but worth a follow-up: the bottom createReactiveStoreFromDataPublisherFactory snippet still shows an abortSignal: property on the config and the bullet about "Triggering the caller's abortSignal disconnects the store permanently" — neither matches the current withSignal()-per-connect API. Not blocking this PR, but worth knowing while the file is open.
  • No other call sites in the repo reference 'retrying' as far as I can see; the type-system removal of the variant would surface any stragglers at compile time.

Comment thread packages/subscribable/README.md Outdated
| { data: T; error: undefined; status: 'loaded' }
| { data: T | undefined; error: unknown; status: 'error' }
| { data: T | undefined; error: undefined; status: 'retrying' }
| { data: T | undefined; error: unknown; status: 'retrying' }
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

This line should be removed, not edited — retrying is no longer a variant of ReactiveState<T>. The PR only flipped error: undefinederror: unknown here, which leaves the README claiming a variant that doesn't exist in reactive-stream-store.ts.

The replacement variant (loading widened to data: T | undefined; error: unknown) needs to be reflected on the existing loading line a few rows up too.

Comment thread packages/subscribable/README.md Outdated
> Also exported as `ReactiveStore<T>` for backwards compatibility. That alias is deprecated and will be removed in a future major release.

The store starts in `status: 'idle'`. Call `connect()` to open the underlying stream; the store will transition through `loading` → `loaded` (or `error`). A subsequent `connect()` after `loaded` or `error` transitions through `retrying` while preserving the last known value. Call `reset()` to tear down the connection and return to `idle` without permanently killing the store.
The store starts in `status: 'idle'`. Call `connect()` to open the underlying stream; the store will transition through `loading` → `loaded` (or `error`). A subsequent `connect()` after `loaded` or `error` transitions through `retrying` while preserving the last known value and the last error (stale-while-revalidate). A subsequent `loaded` clears the error; a subsequent `error` replaces it. Call `reset()` to tear down the connection and return to `idle` without permanently killing the store.
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Still mentions retrying, which no longer exists. Needs to be rewritten along the lines of the source TSDoc — e.g. "A subsequent connect() after loaded or error transitions back through loading while preserving the last known data and error (stale-while-revalidate)...". The A subsequent loaded clears the error; a subsequent error replaces it. sentence afterwards is correct and can stay as-is.

@github-actions
Copy link
Copy Markdown
Contributor

github-actions Bot commented May 20, 2026

Documentation Preview: https://kit-docs-haq8ian6e-anza-tech.vercel.app

Mirrors the action store's `running` (which already merged "first call vs subsequent call"). `data` and `error` are preserved through `loading` for stale-while-revalidate UX.

`ReactiveState<T>` drops the `retrying` variant. `loading` widens from `{ data: undefined, error: undefined }` to `{ data: T | undefined, error: unknown }`. Both `createReactiveStoreFromDataPublisherFactory` and `createReactiveStoreWithInitialValueAndSlotTracking` transition every `connect()` through `loading`, preserving `currentState.data` and `currentState.error`.

Consumers that need to distinguish first-load from reconnect inspect `data === undefined` instead of relying on a separate `retrying` status. Bumps `@solana/subscribable` and `@solana/kit` major.

Tests in both packages updated: `retrying`-state assertions now assert `loading` carrying stale data + error. Downstream `useSubscription` (next commit) drops `retrying` from `SubscriptionResult.status` and passes data + error through the `loading` case.
@mcintyre94 mcintyre94 force-pushed the reactive-store/preserve-retrying-error branch from efde42d to 2fd75aa Compare May 20, 2026 13:34
@mcintyre94
Copy link
Copy Markdown
Member Author

Oops missed some changes to the readme. Please re-review @trevor-cortex

@trevor-cortex
Copy link
Copy Markdown

Re-reviewed the README diff — all three stale retrying references from my previous pass are gone:

  • The ReactiveState<T> block drops the retrying variant and updates loading to { data: T | undefined; error: unknown }.
  • The narrative paragraph now describes the SWR semantic correctly (preserve last data + error on reconnect, loaded clears, error replaces).
  • The bottom "Things to note" bullet is rewritten to match.

Nice bonus: the pre-existing withSignal() / abortSignal: staleness I called out as out-of-scope last round has also been fixed — factory signature, example, and final bullet all reflect the current withSignal(signal).connect() API.

Nothing else to flag. LGTM. 👍

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.

2 participants