Skip to content

Drop auto-connect from stream stores#1662

Open
mcintyre94 wants to merge 1 commit into
react/use-requestfrom
reactive-store/no-auto-connect
Open

Drop auto-connect from stream stores#1662
mcintyre94 wants to merge 1 commit into
react/use-requestfrom
reactive-store/no-auto-connect

Conversation

@mcintyre94
Copy link
Copy Markdown
Member

@mcintyre94 mcintyre94 commented May 19, 2026

Summary of Changes

This PR removes auto-connect from ReactiveStreamStore, such that it starts in an idle state and begins streaming only after .connect() is called. This mirrors the behaviour of the action store, and means that we can add per-connection signals (in a following PR) for subscriptions with a similar API to the action store.

We deprecate .retry(), which was previously only available after an error, in favour of .connect() which now also starts the stream for the first time.

The previously deprecated createReactiveStoreFromDataPublisher, which takes a DataPublisher rather than a () => DataPublisher function and therefore can't be called multiple times, is removed.

@changeset-bot
Copy link
Copy Markdown

changeset-bot Bot commented May 19, 2026

🦋 Changeset detected

Latest commit: e4b9966

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/rpc-subscriptions-spec Major
@solana/kit Major
@solana/react Major
@solana/rpc-spec Major
@solana/rpc-subscriptions-channel-websocket Major
@solana/rpc-subscriptions Major
@solana/plugin-interfaces Major
@solana/rpc-subscriptions-api Major
@solana/accounts Major
@solana/rpc-api Major
@solana/rpc-transport-http Major
@solana/rpc Major
@solana/sysvars 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

@bundlemon
Copy link
Copy Markdown

bundlemon Bot commented May 19, 2026

BundleMon

Files updated (22)
Status Path Size Limits
react/dist/index.browser.mjs
4.33KB (+1.24KB +39.98%) -
react/dist/index.native.mjs
4.33KB (+1.24KB +39.99%) -
react/dist/index.node.mjs
4.33KB (+1.24KB +40.01%) -
subscribable/dist/index.node.mjs
2.8KB (+124B +4.52%) -
subscribable/dist/index.browser.mjs
2.72KB (+122B +4.57%) -
subscribable/dist/index.native.mjs
2.73KB (+122B +4.56%) -
wallet-account-signer/dist/index.browser.mjs
17.57KB (+45B +0.25%) -
wallet-account-signer/dist/index.native.mjs
17.57KB (+45B +0.25%) -
wallet-account-signer/dist/index.node.mjs
17.59KB (+44B +0.24%) -
errors/dist/index.browser.mjs
20.76KB (+36B +0.17%) -
errors/dist/index.native.mjs
20.76KB (+35B +0.16%) -
errors/dist/index.node.mjs
20.78KB (+35B +0.16%) -
rpc-subscriptions-spec/dist/index.browser.mjs
2.18KB (-14B -0.62%) -
rpc-subscriptions-spec/dist/index.native.mjs
2.19KB (-14B -0.62%) -
rpc-subscriptions-spec/dist/index.node.mjs
2.23KB (-15B -0.65%) -
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%) -
@solana/kit production bundle
kit/dist/index.production.min.js
52.4KB (-223B -0.41%) -
kit/dist/index.browser.mjs
4.08KB (-396B -8.65%) -
kit/dist/index.native.mjs
4.08KB (-396B -8.65%) -
kit/dist/index.node.mjs
4.08KB (-396B -8.65%) -
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
871B -
rpc-subscriptions-api/dist/index.browser.mjs
870B -
rpc-subscriptions-api/dist/index.node.mjs
870B -
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 +2.83KB +0.54%

Final result: ✅

View report in BundleMon website ➡️


Current branch size history | Target branch size history

@github-actions
Copy link
Copy Markdown
Contributor

github-actions Bot commented May 19, 2026

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

@mcintyre94 mcintyre94 force-pushed the reactive-store/no-auto-connect branch from 69306ce to 3bccb76 Compare May 19, 2026 12:23
@mcintyre94 mcintyre94 changed the title Drop auto-connect from stream stores; callers invoke connect() explicitly Drop auto-connect from stream stores May 19, 2026
@mcintyre94 mcintyre94 added the major This would require a major version bump label May 19, 2026
@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.

Summary

Drops auto-connect from ReactiveStreamStore so the store starts in idle and only opens its underlying stream when the caller invokes connect(). This mirrors the action-store pattern and sets up per-connection signals in a follow-up PR. retry() is deprecated in favour of connect() (which now both starts the stream and reconnects after an error/load), and a new reset() returns the store to idle without permanently killing it. The deprecated createReactiveStoreFromDataPublisher non-factory variant is removed, along with PendingRpcSubscriptionsRequest.reactive(). Tests across @solana/subscribable, @solana/rpc-subscriptions-spec, and @solana/kit are updated to call connect() explicitly.

State machine summary: idle → (connect) → loadingloaded/error; from loaded/error → (connect) → retryingloaded/error; from anywhere → (reset) → idle; once abortSignal fires, all connect()s are no-ops.

Things to watch out for

  1. Missing @solana/kit entry in the changeset. packages/kit/src/create-reactive-store-with-initial-value-and-slot-tracking.ts now starts in idle and requires an explicit connect() from callers — that's a breaking behavioural change for @solana/kit consumers, but the changeset only lists @solana/subscribable and @solana/rpc-subscriptions-spec. Kit will still bump via the fixed config, but the changelog won't tell consumers why their code stopped firing. Worth adding '@solana/kit': major and a short migration note (store.connect() after construction, or wrap in useEffect).

  2. Doc-comment artifact for createDataPublisher in FactoryConfig. The JSDoc has two trailing blank * lines and the // FIXME block (originally above the now-removed dataPublisher field) is sandwiched between the JSDoc and createDataPublisher: .... The FIXME also still refers to "dataPublisher" by name. See inline.

  3. loadingretrying with data: undefined when connect() is called during the initial connection. Calling connect() while still in loading (factory hasn't resolved yet) transitions to retrying with data: undefined, even though no prior outcome has occurred. The state-machine doc comment says retrying is entered "after a previous outcome" — there isn't one in this case. This is a minor semantic wrinkle rather than a bug; worth confirming the intent and either tightening the doc, or short-circuiting connect() to leave the store in loading when called from loading. See inline.

Notes for subsequent reviewers

  • SOLANA_ERROR__SUBSCRIBABLE__RETRY_NOT_SUPPORTED (8195000) is now orphaned but correctly retained in packages/errors/src/codes.ts and the SolanaErrorCode union, per the repo's "never remove error codes" policy. The corresponding message in messages.ts should also still be present — worth a quick check that it wasn't removed.
  • The setState equality short-circuit (status/data/error triple compare) prevents spurious subscriber notifications, which is a nice cleanup vs the previous direct currentState = ...; notify(); pattern.
  • currentInnerController?.abort() cleanly tears down the prior connection on every connect() and reset(), so listeners on the old DataPublisher are removed before the new connection wires up — covered by the aborts the prior connection when called again before data arrives test.
  • In the slot-tracking variant, lastUpdateSlot is intentionally preserved across connect() reconnections (so the store never regresses) but reset on reset(). The comment in performReset calls this out; worth confirming this is the desired semantics, since a user using reset() to switch contexts vs to recover the same context might want different behaviours.
  • retry() being kept as a deprecated alias is a reasonable migration ramp, but two semantically-different methods on the same store (connect() always reconnects; retry() only from error) may surprise consumers. The deprecation note is good. Worth confirming this is a one-release transitional state rather than a permanent dual-method shape.
  • Test coverage on the new factory store is solid (idle/connect/reset/retry/abort all covered, including the in-flight-connect-then-reconnect case). The slot-tracking tests are still using the deprecated retry() rather than connect() for the error-recovery scenarios — not wrong (it's the deprecated alias path), but worth double-checking that the connect() recovery path is exercised at least once in that test file too.

Comment on lines 12 to 22
/**
* Messages from this channel of `dataPublisher` will be used to update the store's state.
* An async factory that produces a fresh {@link DataPublisher} each time it is invoked. Called
* on every {@link ReactiveStreamStore.connect | `connect()`}. Rejections surface as a store
* error.
*
*
*/
dataChannelName: string;
// FIXME: It would be nice to be able to constrain the type of `dataPublisher` to one that
// definitely supports the `dataChannelName` and `errorChannelName` channels, and
// furthermore publishes `TData` on the `dataChannelName` channel. This is more difficult
// than it should be: https://tsplay.dev/NlZelW
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Two small doc-comment cleanups in this block:

  1. The JSDoc for createDataPublisher ends with two trailing blank * lines (* error.\n *\n *\n */) — looks like a leftover from the merge. Should be a single closing line.
  2. The // FIXME block (lines 18–21 of this hunk) was originally above the now-removed dataPublisher: DataPublisher; field and refers to "dataPublisher" by name. It's now orphaned mid-declaration, sitting between the JSDoc for createDataPublisher and the createDataPublisher field itself. TypeDoc will probably either drop it or attach it to the wrong member. Either move the FIXME above the JSDoc (and reword to mention createDataPublisher / the factory return type), or delete it if the constraint no longer applies in the factory-only world.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

Still relevant, applies to the return of createDataPublisher. Reworded a bit and moved above the jsdoc. Intentionally not part of the jsdoc though.

* - `retrying`: a follow-up `connect()` is in progress after a previous outcome. `error` is
* cleared; `data` is preserved from the previous connection if any.
*/
export type ReactiveState<T> =
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Minor semantic wrinkle: the doc says retrying is "in progress after a previous outcome", but performConnect will transition loading → retrying if connect() is called a second time before the first connection resolves (since the else branch in performConnect fires for any non-idle status, including loading). At that point data is still undefined and no outcome has occurred yet.

Two options worth considering:

  • Tighten the doc to acknowledge that retrying can also be entered from in-flight loading, or
  • Short-circuit performConnect so that re-entering from loading stays in loading (only swap the inner controller / factory invocation, no status transition).

Not blocking — just worth picking one explicitly so the state-machine docs and the implementation agree.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

Agree with this, updated so it stays in loading.

@@ -0,0 +1,22 @@
---
'@solana/subscribable': major
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

createReactiveStoreWithInitialValueAndSlotTracking in @solana/kit is also breaking — it no longer auto-connects, and every caller now needs store.connect() after construction. Kit will bump via the fixed config anyway, but the changelog entry won't surface the migration to consumers.

Suggest adding '@solana/kit': major to the frontmatter and a short paragraph explaining the migration (e.g. "createReactiveStoreWithInitialValueAndSlotTracking no longer fires the RPC request on construction — call store.connect() to start it, or wrap in a useEffect that calls connect() on mount and reset() on cleanup.").

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

Fair, added @solana/kit: major

Copy link
Copy Markdown
Member

@lorisleiva lorisleiva left a comment

Choose a reason for hiding this comment

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

Love it! Consistent APIs 🤤

…icitly

`createReactiveStoreFromDataPublisherFactory` (and `createReactiveStoreWithInitialValueAndSlotTracking`) no longer auto-connect on construction. The returned store starts in `status: 'idle'`; the caller calls `store.connect()` to fire the underlying request/subscription, mirroring how `ReactiveActionStore` requires an explicit `dispatch()`. This unifies the lifecycle model across both primitives — stores are state machines that callers orchestrate, not self-starting subscriptions — and lets `useSubscription` adopt the same `useEffect(() => { store.connect(); return () => store.reset(); })` pattern that `useRequest` uses today.

`ReactiveStreamStore<T>` gains two new methods: `connect()` (open or re-open the stream — transitions through `loading` from `idle` or `retrying` from any other status while preserving stale data) and `reset()` (abort the current connection, clearing `data` and `error`, and return to `idle`). The state union grows an `idle` variant. `retry()` becomes a deprecated alias that delegates to `connect()` guarded by `status === 'error'` — preserving its original error-only behaviour for callers who relied on it, but new code should call `connect()` directly.

`createReactiveStoreFromDataPublisher` (the non-factory variant accepting a ready-made `DataPublisher`) is removed. Its only documented consumer was the deprecated `PendingRpcSubscriptionsRequest.reactive()` method, which is also removed in this commit. Both have been deprecated for some time; the cascade removal lands them together rather than ratcheting through two more release cycles. Code that built a store around a singleton publisher should wrap it in `createDataPublisher: () => Promise.resolve(publisher)` and use the factory variant.

`createReactiveStoreWithInitialValueAndSlotTracking` is also lazy: starts in `idle`, requires `connect()` to fire the RPC request and open the subscription. The `lastUpdateSlot` window resets on `reset()` so a fresh `connect()` starts a new slot-tracking lifecycle. Tests in `@solana/subscribable`, `@solana/rpc-subscriptions-spec`, and `@solana/kit` updated accordingly (the `reactive()` test block in `rpc-subscriptions-spec` is removed).

The error code `SOLANA_ERROR__SUBSCRIBABLE__RETRY_NOT_SUPPORTED` is left in the codes table per the never-remove-error-codes rule, but is no longer produced by any code path in this repo.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

major This would require a major version bump

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants