Skip to content

Add framework-agnostic source types for reactive bindings#1606

Merged
mcintyre94 merged 1 commit into
mainfrom
react/types
May 8, 2026
Merged

Add framework-agnostic source types for reactive bindings#1606
mcintyre94 merged 1 commit into
mainfrom
react/types

Conversation

@mcintyre94
Copy link
Copy Markdown
Member

@mcintyre94 mcintyre94 commented May 7, 2026

Summary of Changes

This PR adds some types that are useful for reactive UI consumers:

  • ReactiveStreamSource<T> — anything with a reactiveStore({ abortSignal }) method that returns a ReactiveStreamStore<T>. PendingRpcSubscriptionsRequest<T> satisfies this by design.
  • ReactiveActionSource<T> — anything with a zero-argument reactiveStore() method that returns a ReactiveActionStore<[], T>. PendingRpcRequest<T> satisfies this by design.

These are intended to allow plugins to expose their own functionality to reactive UI acting on actions/streams, without having to fit the precise PendingRpc[Subscriptions]Request types. React UI hooks will use these types as input.

CreateReactiveStoreWithInitialValueAndSlotTrackingConfig is now exported from @solana/kit, was private. This type is useful for plugins to return, eg kit-plugin-rpccan provide a function that returns the config for a useBalance type subscription in this shape, which can then be used with any reactive UI, without depending on @solana/react.

@changeset-bot
Copy link
Copy Markdown

changeset-bot Bot commented May 7, 2026

🦋 Changeset detected

Latest commit: 4085fd5

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 Minor
@solana/kit Minor
@solana/rpc-spec Minor
@solana/rpc-subscriptions-channel-websocket Minor
@solana/rpc-subscriptions-spec Minor
@solana/rpc-subscriptions Minor
@solana/accounts Minor
@solana/plugin-interfaces Minor
@solana/rpc-api Minor
@solana/rpc-transport-http Minor
@solana/rpc Minor
@solana/sysvars Minor
@solana/rpc-subscriptions-api Minor
@solana/transaction-confirmation Minor
@solana/program-client-core Minor
@solana/rpc-graphql 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/fixed-points 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-parsed-types Minor
@solana/rpc-spec-types Minor
@solana/rpc-transformers Minor
@solana/rpc-types Minor
@solana/signers 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 May 7, 2026

@bundlemon
Copy link
Copy Markdown

bundlemon Bot commented May 7, 2026

BundleMon

Unchanged files (147)
Status Path Size Limits
@solana/kit production bundle
kit/dist/index.production.min.js
52.24KB -
errors/dist/index.node.mjs
20.54KB -
errors/dist/index.browser.mjs
20.52KB -
errors/dist/index.native.mjs
20.52KB -
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
17.38KB -
wallet-account-signer/dist/index.browser.mjs
17.37KB -
wallet-account-signer/dist/index.native.mjs
17.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 -
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 -
kit/dist/index.browser.mjs
3.97KB -
kit/dist/index.native.mjs
3.97KB -
kit/dist/index.node.mjs
3.97KB -
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 -
react/dist/index.browser.mjs
3.09KB -
react/dist/index.native.mjs
3.09KB -
react/dist/index.node.mjs
3.09KB -
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 -
subscribable/dist/index.node.mjs
2.68KB -
subscribable/dist/index.native.mjs
2.61KB -
subscribable/dist/index.browser.mjs
2.6KB -
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 -
rpc-subscriptions-spec/dist/index.node.mjs
2.25KB -
rpc-subscriptions-spec/dist/index.native.mjs
2.2KB -
rpc-subscriptions-spec/dist/index.browser.mjs
2.2KB -
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
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-spec/dist/index.browser.mjs
918B -
rpc-spec/dist/index.native.mjs
918B -
rpc-spec/dist/index.node.mjs
917B -
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 -

No change in files bundle size

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 7, 2026

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

@mcintyre94 mcintyre94 requested a review from lorisleiva May 7, 2026 17:21
@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

Adds two structural duck-types in @solana/subscribable that decouple reactive-framework bindings from the concrete PendingRpcRequest / PendingRpcSubscriptionsRequest producer types:

  • ReactiveStreamSource<T> — anything with reactiveStore({ abortSignal }) returning a ReactiveStreamStore<T>.
  • ReactiveActionSource<T> — anything with a zero-arg reactiveStore() returning a ReactiveActionStore<[], T>.

Also promotes CreateReactiveStoreWithInitialValueAndSlotTrackingConfig from file-private to a public export so non-React consumers can name the config shape, and adds two compile-only typetests asserting the producer types satisfy the new duck-types.

The changes are purely additive types — no runtime impact, tree-shake-safe. Both producer types already happen to match the duck-types exactly, so the typetests should hold without any signature changes.

Things to watch out for

  • ReactiveStreamSource / ReactiveActionSource are not re-exported from @solana/kit. packages/kit/src/index.ts doesn't blanket re-export @solana/subscribable, and nothing in this PR adds these two types to the kit barrel. Given that the PR description positions them as the duck-types React hooks and plugin authors will consume — and plugin authors typically import from @solana/kit — this looks like a gap worth closing in this PR (or worth an explicit decision to require importing from @solana/subscribable directly). The @solana/kit minor bump in the changeset is still justified by the newly-exported CreateReactiveStoreWithInitialValueAndSlotTrackingConfig, so this is about API surface, not the bump.

  • Per-repo JSDoc convention. CLAUDE.md requires @typeParam on exported generics and an @example when helpful. The new ReactiveStreamSource<T> / ReactiveActionSource<T> types have prose docblocks but no @typeParam T and no @example. The promoted CreateReactiveStoreWithInitialValueAndSlotTrackingConfig is fully documented and is a good model.

  • Inlined options shape. ReactiveStreamSource inlines { abortSignal: AbortSignal } rather than reusing RpcSubscribeOptions. That's the right call here — @solana/subscribable mustn't depend on @solana/rpc-subscriptions-spec — but it does mean the duck-type and the producer share the shape only structurally. If RpcSubscribeOptions ever grows a non-optional field, the duck-type silently won't match anymore. Not an issue today; worth keeping in mind.

Notes for subsequent reviewers

  • Verify whether the intent is to expose these new types via @solana/kit (likely yes, given the PR motivation). A one-line addition to packages/kit/src/index.ts re-exporting @solana/subscribable (or a targeted export type { ... }) would do it.
  • The two new typetest files use the existing repo idiom (null as unknown as X satisfies Y) and are correctly placed in packages that already depend on @solana/subscribable — no package.json changes needed.
  • No runtime code paths are touched, so existing tests should be sufficient; the compile-time tests are the meaningful coverage here.

Comment on lines +134 to +144
/**
* Duck-type for objects that build a {@link ReactiveStreamStore} on demand via a
* `reactiveStore({ abortSignal })` method. Satisfied by `PendingRpcSubscriptionsRequest<T>`.
* Reactive-framework bindings (e.g. React's `useSubscription`) consume this duck-type so they
* don't have to name a concrete producer type.
*
* @see {@link ReactiveStreamStore}
* @see {@link ReactiveActionSource}
*/
export type ReactiveStreamSource<T> = {
reactiveStore(options: { abortSignal: AbortSignal }): ReactiveStreamStore<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.

Per the repo's JSDoc convention (see CLAUDE.md → "TypeScript Docblocks"): exported generic types should carry @typeParam for each type parameter, and an @example where helpful. Suggest adding:

/**
 * ...existing prose...
 *
 * @typeParam T - The notification value type emitted by the produced {@link ReactiveStreamStore}.
 *
 * @example
 * ```ts
 * function bindStream<T>(source: ReactiveStreamSource<T>, abortSignal: AbortSignal) {
 *     const store = source.reactiveStore({ abortSignal });
 *     return store.subscribe(() => { /* ... *\/ });
 * }
 * ```
 */

(The promoted CreateReactiveStoreWithInitialValueAndSlotTrackingConfig in this same PR is a good model — fully tagged with @typeParam × 3.)

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.

Updated these

Comment thread packages/subscribable/src/reactive-action-store.ts
// `PendingRpcSubscriptionsRequest<T>` is structurally assignable to `ReactiveStreamSource<T>` —
// the duck-type reactive-framework bindings consume so they don't have to name a concrete
// producer type.
null as unknown as PendingRpcSubscriptionsRequest<number> satisfies ReactiveStreamSource<number>;
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: as a stronger signal of intent, consider also asserting the inverse direction is not required — i.e. that a ReactiveStreamSource<T> is not assignable back to PendingRpcSubscriptionsRequest<T>. Something like:

// @ts-expect-error — the duck-type does not require `subscribe()` / `reactive()`.
null as unknown as ReactiveStreamSource<number> satisfies PendingRpcSubscriptionsRequest<number>;

This pins down the "strictly broader" relationship and would catch a future change that accidentally tightens ReactiveStreamSource to require methods that only the concrete producer has. Not blocking — the existing assertion already covers the contract the bindings rely on.

Adds `ReactiveStreamSource<T>` and `ReactiveActionSource<T>` to `@solana/subscribable` as framework-agnostic duck-types over the existing `.reactiveStore()` methods on pending RPC requests and subscriptions. Reactive-framework bindings (React's `useSubscription` / `useRequest`, plus future Vue / Svelte / Solid bindings) consume these instead of naming concrete producer types, and plugin authors get a stable contract for their own pending-request objects.

Also promotes `CreateReactiveStoreWithInitialValueAndSlotTrackingConfig` from a private alias in `@solana/kit` to a public export. `kit-plugin-rpc`'s upcoming spec builders (`createBalanceLiveData`, `createAccountLiveData`, `createTransactionConfirmationLiveData`) declare their return shape as `Omit<CreateReactiveStoreWithInitialValueAndSlotTrackingConfig<...>, 'abortSignal'>` so the consuming hook supplies the abort signal — letting kit-plugin-rpc avoid a dependency on `@solana/react`.

Type-only additions; no runtime changes. Type tests in `@solana/rpc-spec` and `@solana/rpc-subscriptions-spec` confirm the structural assignability.
@mcintyre94
Copy link
Copy Markdown
Member Author

ReactiveStreamSource / ReactiveActionSource are not re-exported from @solana/kit. packages/kit/src/index.ts doesn't blanket re-export @solana/subscribable, and nothing in this PR adds these two types to the kit barrel. Given that the PR description positions them as the duck-types React hooks and plugin authors will consume — and plugin authors typically import from @solana/kit — this looks like a gap worth closing in this PR (or worth an explicit decision to require importing from @solana/subscribable directly). The @solana/kit minor bump in the changeset is still justified by the newly-exported CreateReactiveStoreWithInitialValueAndSlotTrackingConfig, so this is about API surface, not the bump.

This is intentional - we're exporting these from @solana/subscribable and not re-exporting that package from Kit.

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.

Good shout!

@mcintyre94 mcintyre94 merged commit da868aa into main May 8, 2026
14 checks passed
@mcintyre94 mcintyre94 deleted the react/types branch May 8, 2026 16:16
@github-actions
Copy link
Copy Markdown
Contributor

github-actions Bot commented May 8, 2026

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

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