Skip to content

Add reactive() for pending subscriptions, that returns a reactive store for the subscription#1524

Merged
mcintyre94 merged 1 commit into
mainfrom
subscriptions-reactive
Apr 8, 2026
Merged

Add reactive() for pending subscriptions, that returns a reactive store for the subscription#1524
mcintyre94 merged 1 commit into
mainfrom
subscriptions-reactive

Conversation

@mcintyre94
Copy link
Copy Markdown
Member

@mcintyre94 mcintyre94 commented Apr 7, 2026

Summary of Changes

This PR adds an alternative API for RPC subscriptions:

const store = rpcSubscriptions.slotNotifications().reactive({ abortSignal: abortController.signal })

This returns an object with fields getState(), getError() and subscribe()

  • getState() returns the latest published value from the subscription, or undefined until a value is published
  • getError() returns the first error from the subscription, or undefined if there has not been any error
  • subscribe(listener)registers a function to be called if either data or an error occurs

Together they enable reactive frameworks to consume subscriptions:, eg react with an error boundary

const slot = useSyncExternalStore(store.subscribe, () => {
  if (store.getError()) throw store.getError();
  return store.getState();
});

Or without error boundaries:

const slot = useSyncExternalStore(store.subscribe, store.getState)
const error = useSyncExternalStore(store.subscribe, store.getError)

And svelte:

let slot = $state(store.getState());
let error = $state(store.getError());
$effect(() => store.subscribe(() => {
    slot = store.getState();
    error = store.getError();
}));

Vanilla JS:

store.subscribe(() => {
    if (store.getError()) console.error(store.getError());
    else el.textContent = store.getState().slot;
});

It's also compatible with libraries like SWR subscriptions:

useSWRSubscription('slot', (key, { next }) => {
    const abortController = new AbortController();
    rpcSubscriptions
        .slotNotifications()
        .reactive({ abortSignal: abortController.signal })
        .then(store => {
            store.subscribe(() => {
                const error = store.getError();
                if (error) next(error);
                else next(null, store.getState());
            });
        });
    return () => abortController.abort();
});

Note that we intentionally separate error handling, and getState() never throws. This gives consumers maximum flexibility and allows access to the stale latest value while recovering from an error.

This change is purely additive to the existing async generator API for subscriptions, and architecturally lives alongside it as an alternative interface to the DataPublisher abstraction. It's intended to make subscriptions easier to use in reactive UIs.

@changeset-bot
Copy link
Copy Markdown

changeset-bot Bot commented Apr 7, 2026

🦋 Changeset detected

Latest commit: 8ec21aa

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

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

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

Copy link
Copy Markdown
Member Author

mcintyre94 commented Apr 7, 2026

This stack of pull requests is managed by Graphite. Learn more about stacking.

@bundlemon
Copy link
Copy Markdown

bundlemon Bot commented Apr 7, 2026

BundleMon

Files updated (7)
Status Path Size Limits
subscribable/dist/index.node.mjs
1.97KB (+177B +9.61%) -
subscribable/dist/index.browser.mjs
1.91KB (+175B +9.81%) -
subscribable/dist/index.native.mjs
1.92KB (+174B +9.73%) -
@solana/kit production bundle
kit/dist/index.production.min.js
46.39KB (+127B +0.27%) -
rpc-subscriptions-spec/dist/index.browser.mjs
2.16KB (+36B +1.65%) -
rpc-subscriptions-spec/dist/index.native.mjs
2.17KB (+33B +1.51%) -
rpc-subscriptions-spec/dist/index.node.mjs
2.21KB (+32B +1.44%) -
Unchanged files (135)
Status Path Size Limits
errors/dist/index.node.mjs
19.34KB -
errors/dist/index.browser.mjs
19.32KB -
errors/dist/index.native.mjs
19.32KB -
rpc-graphql/dist/index.browser.mjs
18.82KB -
rpc-graphql/dist/index.native.mjs
18.81KB -
rpc-graphql/dist/index.node.mjs
18.81KB -
wallet-account-signer/dist/index.node.mjs
16.4KB -
wallet-account-signer/dist/index.browser.mjs
16.37KB -
wallet-account-signer/dist/index.native.mjs
16.37KB -
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 -
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 -
rpc-transformers/dist/index.browser.mjs
3.16KB -
rpc-transformers/dist/index.native.mjs
3.16KB -
rpc-transformers/dist/index.node.mjs
3.16KB -
signers/dist/index.browser.mjs
3.14KB -
signers/dist/index.native.mjs
3.14KB -
signers/dist/index.node.mjs
3.14KB -
react/dist/index.browser.mjs
3.09KB -
react/dist/index.native.mjs
3.09KB -
react/dist/index.node.mjs
3.09KB -
addresses/dist/index.browser.mjs
2.93KB -
addresses/dist/index.native.mjs
2.92KB -
addresses/dist/index.node.mjs
2.92KB -
kit/dist/index.browser.mjs
2.78KB -
kit/dist/index.native.mjs
2.78KB -
kit/dist/index.node.mjs
2.78KB -
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.41KB -
sysvars/dist/index.browser.mjs
2.37KB -
sysvars/dist/index.native.mjs
2.37KB -
sysvars/dist/index.node.mjs
2.37KB -
transaction-confirmation/dist/index.native.mj
s
2.36KB -
transaction-confirmation/dist/index.browser.m
js
2.35KB -
keys/dist/index.browser.mjs
2.1KB -
keys/dist/index.native.mjs
2.1KB -
keys/dist/index.node.mjs
2.1KB -
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/dist/index.browser.mjs
1.8KB -
rpc-transport-http/dist/index.node.mjs
1.72KB -
rpc-types/dist/index.browser.mjs
1.53KB -
rpc-types/dist/index.native.mjs
1.53KB -
rpc-types/dist/index.node.mjs
1.53KB -
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
976B -
rpc-api/dist/index.native.mjs
975B -
rpc-api/dist/index.node.mjs
973B -
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 -
rpc-spec/dist/index.browser.mjs
852B -
rpc-spec/dist/index.native.mjs
851B -
rpc-spec/dist/index.node.mjs
850B -
promises/dist/index.browser.mjs
799B -
promises/dist/index.native.mjs
798B -
promises/dist/index.node.mjs
797B -
assertions/dist/index.browser.mjs
783B -
instructions/dist/index.browser.mjs
771B -
instructions/dist/index.native.mjs
770B -
instructions/dist/index.node.mjs
768B -
plugin-core/dist/index.browser.mjs
749B -
plugin-core/dist/index.native.mjs
749B -
plugin-core/dist/index.node.mjs
747B -
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 -
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
137B -
codecs/dist/index.native.mjs
136B -
codecs/dist/index.node.mjs
134B -
event-target-impl/dist/index.browser.mjs
133B -
ws-impl/dist/index.node.mjs
131B -
text-encoding-impl/dist/index.browser.mjs
122B -
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 +754B +0.15%

Final result: ✅

View report in BundleMon website ➡️


Current branch size history | Target branch size history

@mcintyre94 mcintyre94 force-pushed the subscriptions-reactive branch 2 times, most recently from 1f7053c to 7594c68 Compare April 7, 2026 09:37
@mcintyre94 mcintyre94 requested a review from lorisleiva April 7, 2026 09:38
@mcintyre94 mcintyre94 marked this pull request as ready for review April 7, 2026 09:38
@mcintyre94 mcintyre94 force-pushed the subscriptions-reactive branch 3 times, most recently from 5c10989 to 632c232 Compare April 7, 2026 10:06
@mcintyre94 mcintyre94 changed the title Add a reactive() for pending subscriptions, that returns a reactive store for the suscription Add reactive() for pending subscriptions, that returns a reactive store for the suscription Apr 7, 2026
@mcintyre94 mcintyre94 changed the title Add reactive() for pending subscriptions, that returns a reactive store for the suscription Add reactive() for pending subscriptions, that returns a reactive store for the subscription Apr 7, 2026
@github-actions
Copy link
Copy Markdown
Contributor

github-actions Bot commented Apr 7, 2026

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

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.

This is amazing! And so much cleaner than what I thought it was going to be. Just left a comment on the error handling side but happy to merge as-is.

Comment thread packages/subscribable/src/reactive-store.ts
@mcintyre94 mcintyre94 force-pushed the subscriptions-reactive branch from 632c232 to 20263d3 Compare April 8, 2026 12:56
@mcintyre94 mcintyre94 force-pushed the subscriptions-reactive branch from 20263d3 to 8ec21aa Compare April 8, 2026 12:57
Copy link
Copy Markdown
Member Author

mcintyre94 commented Apr 8, 2026

Merge activity

  • Apr 8, 1:12 PM UTC: A user started a stack merge that includes this pull request via Graphite.
  • Apr 8, 1:12 PM UTC: @mcintyre94 merged this pull request with Graphite.

@mcintyre94 mcintyre94 merged commit f53ce07 into main Apr 8, 2026
14 checks passed
@mcintyre94 mcintyre94 deleted the subscriptions-reactive branch April 8, 2026 13:12
@github-actions
Copy link
Copy Markdown
Contributor

github-actions Bot commented Apr 8, 2026

🔎💬 Inkeep AI search and chat service is syncing content for source 'Solana Kit Docs'

@github-actions
Copy link
Copy Markdown
Contributor

Because there has been no activity on this PR for 14 days since it was merged, it has been automatically locked. Please open a new issue if it requires a follow up.

@github-actions github-actions Bot locked as resolved and limited conversation to collaborators Apr 23, 2026
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants