Skip to content

Add useSubscriptionSwr to the @solana/react/swr adapter#1710

Closed
mcintyre94 wants to merge 16 commits into
react/use-request-swrfrom
react/use-subscription-swr
Closed

Add useSubscriptionSwr to the @solana/react/swr adapter#1710
mcintyre94 wants to merge 16 commits into
react/use-request-swrfrom
react/use-subscription-swr

Conversation

@mcintyre94

Copy link
Copy Markdown
Member

Problem

Summary of Changes

Fixes #

mcintyre94 added 16 commits May 26, 2026 08:40
Adds the Kit client context layer for `@solana/react`: a single provider that publishes a caller-owned Kit client to its subtree, plus two hooks — `useClient` (basic accessor with optional generic narrowing) and `useClientCapability` (runtime-checked accessor with a structured missing-capability error). Required by these hooks and by any plugin-specific hook that depends on a client capability; generic primitives like `useAction` (forthcoming) work against arbitrary async functions and don't need a provider.

The provider accepts both synchronous clients and promise-returning ones — when given a promise (e.g. `createClient().use(asyncPlugin())`) it suspends via the nearest `<Suspense>` boundary until the client resolves. On React 19 it delegates to `React.use(promise)`; on React 18 an internal thrown-promise shim, keyed by promise identity, honours the same contract.

Two new `SolanaError` codes are reserved in the `[9000000-9000999]` range: `SOLANA_ERROR__REACT__MISSING_PROVIDER` (thrown by `useClient` outside a provider) and `SOLANA_ERROR__REACT__MISSING_CAPABILITY` (thrown by `useClientCapability` with `hookName` + `providerHint` context, listing every missing capability when an array is passed).

Additive; no impact on the existing wallet-account hooks. Verified with browser + node unit tests and TS-only typetests across both `@solana/react` and `@solana/errors`.
Bridges an arbitrary async function into a reactive `ActionResult<TArgs, TResult>` with `send` / `status` / `data` / `error` / `reset`. Each `send(...)` runs the function with a fresh `AbortSignal` and tracks its lifecycle through React state; a second `send` while a first is in flight aborts the first.

`fn` is held in a ref that always points at the latest render's closure — there is no `deps` array to maintain. Each `send(...)` invokes the most recently rendered `fn`, so values captured inside (form state, route params, etc.) are always fresh. This matches the convention used by `useMutation` in TanStack Query and `useWriteContract` in wagmi. In-flight calls are unaffected — they continue with the closure they captured at dispatch time.

Built on `createReactiveActionStore` from `@solana/subscribable`. Awaiters of a superseded call see a rejection with an `AbortError` filterable via `isAbortError` from `@solana/promises`.
`reactiveStore()` no longer dispatches the request automatically when called. It now returns a `ReactiveActionStore` in the `idle` state, leaving the caller responsible for the initial `dispatch()`.

The auto-fire was created to make `reactiveStore()` parallel to `send()` — the consumer got a "live" store back the same way they get a live promise. In practice the auto-fire created an asymmetry at the start of the store's lifecycle: the initial request had no caller-visible dispatch site, so attaching an `AbortSignal` to that one specific attempt required a separate construction-time option distinct from the mechanism used for all subsequent attempts. Without the auto-fire, every dispatch is the caller's, and signal attachment lives uniformly at the dispatch site.

This also brings `reactiveStore()` in line with `createReactiveActionStore(fn)` (which doesn't auto-fire either), so the two creation paths now have identical "construct then dispatch" semantics. React hooks consuming `reactiveStore` (e.g. `useRequest`) handle the dispatch internally — consumers of those hooks see no change.

Major version bump for `@solana/rpc-spec`: callers using `reactiveStore()` directly will need to add an explicit `store.dispatch()` to keep the existing fire-on-creation behavior.
…tion

`ReactiveActionStore` now exposes `withSignal(signal)` — a thin wrapper that returns `{ dispatch, dispatchAsync }` bindings composing the caller's signal with the store's internal per-dispatch controller via `AbortSignal.any`. Aborting either cancels the in-flight call and surfaces the abort reason on state. The bare `dispatch` / `dispatchAsync` signatures are unchanged, so this is additive — no existing caller breaks.

The wrapper supports two patterns. Per-attempt timeout: `store.withSignal(AbortSignal.timeout(5_000)).dispatch(args)` — a fresh clock per call, with different call sites free to pass different timeouts without rebuilding the store. Shared kill switch: hold one `AbortController`, bind the wrapper once (`const killable = store.withSignal(killCtrl.signal)`), use `killable.dispatch(...)` everywhere; aborting the controller cancels the in-flight call and short-circuits future calls on that wrapper.

`PendingRpcRequest.reactiveStore()` (and the `ReactiveActionSource` duck-type) also accepts an optional `{ signal?: AbortSignal }` so a caller-provided cancellation source can be attached to the initial dispatch fired implicitly by `reactiveStore()` — same role `abortSignal` plays for `send()`. The `signal` is not a store-level setting; subsequent dispatches on the returned store go through `store.withSignal(...).dispatch(...)` like any other store.

The shared `@solana/test-config` browser environment polyfills `AbortSignal.any` because jsdom 22 (the version pinned here) doesn't ship it. Replacing the AbortSignal class wholesale would break jsdom's brand checks for `addEventListener({ signal })`, so the patch is limited to the missing static method.
Errors from a `ReactiveActionStore` now persist through a subsequent `running` state, matching the existing behavior for `data`. A re-dispatch after a failure keeps the previous error in `state.error` until the new attempt resolves — `success` clears it, a new failure replaces it. This mirrors how SWR and TanStack Query handle revalidation: stale-while-revalidate applies symmetrically to data and errors, so consumers don't have to choose between flickering and losing context on retry.

The `running` variant of `ReactiveActionState<T>` widens from `error: undefined` to `error: unknown`, which surfaces through `useAction` as a behavior change: the `error` field now persists across a new `send(...)` call until that call resolves, instead of clearing immediately. The README, JSDoc, and tests are updated to match. `useRequest` will pick this up via a follow-up commit on the next branch in the stack.
`useRequest(source)` fires a request on mount and re-fires whenever the source identity changes. The source is either a `ReactiveActionSource<T>` (the `{ reactiveStore() }` duck-type — `PendingRpcRequest` is the canonical implementation) or an async function `(signal: AbortSignal) => Promise<T>` for wrapping arbitrary one-shot async work (a `fetch`, a third-party SDK call). Memoize the input with `useMemo` (source) or `useCallback` (function) keyed on its inputs; `react-hooks/exhaustive-deps` then catches stale closures by default. Pass `null` to disable.

The hook returns `{ data, error, refresh, status }` with `status` as one of `fetching | success | error | disabled`. `refresh()` dispatches on the same underlying action store, so its built-in stale-while-revalidate keeps `data` and `error` populated through the next `fetching` attempt — the bridge doesn't mirror state externally.

Optional `getAbortSignal: () => AbortSignal` is invoked on every attempt (initial fire + every `refresh()`). The returned signal is composed with the store's per-dispatch controller via `AbortSignal.any` through `withSignal(signal).dispatch()`. The natural use is per-attempt timeouts (`() => AbortSignal.timeout(5_000)`), which reset on refresh. The factory is held in a ref synced to the latest render — inline closures are fine. `refresh()` accepts an optional `{ abortSignal }` override for one specific attempt; presence-based semantics distinguish \"use factory\" (omit the key), \"explicit signal\" (\`{ abortSignal: signal }\`), and \"no signal\" (\`{ abortSignal: undefined }\`).

Disabling and unmount cancellation are handled the React-native way: pass \`null\` for the source (result reports \`disabled\`), or let the component unmount (effect cleanup aborts the in-flight call). The \`disabledActionStore\` returned for the null-source case has no-op \`dispatch\` and \`reset\` — pinned by a new test file so the \`useRequest\` effect can rely on the invariant without an explicit gate.

Lifts the \`useIsomorphicLayoutEffect\` helper that was duplicated in \`useAction.ts\` into a shared internal module so both hooks share a single definition.

Exports \`RequestResult<T>\` and \`UseRequestOptions\` for plugin hooks that build on this surface.
…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.
…lation

Adds `store.withSignal(signal).connect()` to `ReactiveStreamStore`, mirroring the action store's per-dispatch `withSignal()` pattern — callers attach a per-connection signal at the call site instead of baking one into the store's construction. Drops the construction-time `abortSignal` option on `createReactiveStoreFromDataPublisherFactory`, `createReactiveStoreWithInitialValueAndSlotTracking`, and `PendingRpcSubscriptionsRequest.reactiveStore()`. The duck-type `ReactiveStreamSource<T>.reactiveStore()` is now parameter-less, mirroring `ReactiveActionSource<T>.reactiveStore()`.

`store.withSignal(signal)` returns a thin wrapper exposing `connect()`. Each call composes the caller-provided signal with the per-connection inner controller via `AbortSignal.any` — aborting either tears down the active connection. Aborting the caller-provided signal surfaces the abort reason on state as `{ status: 'error' }`; supersession via the internal controller (a newer `connect()` or `reset()`) stays silent so the newer call owns state. Use cases:

- Per-connection timeout: `store.withSignal(AbortSignal.timeout(30_000)).connect()` mints a fresh clock per attempt.
- Permanent kill switch: hold one `AbortController`, bind the wrapper once (\`const killable = store.withSignal(killCtrl.signal)\`), and use \`killable.connect()\` everywhere. After \`killCtrl.abort()\`, every future call short-circuits to error. Bare \`store.connect()\` calls bypass the bound signal — same discipline contract as the action store's bind-once pattern.

\`createDataPublisher\` is widened from \`() => Promise<DataPublisher>\` to \`(signal: AbortSignal) => Promise<DataPublisher>\`. The store passes the composed per-connection signal to the factory so the underlying transport can stop on per-connection abort, not just the stream-store's listeners. Existing no-arg factories still satisfy the new shape — TypeScript allows fewer parameters than the declared type.

Internal: the per-connection inner controller plus the optional caller signal are composed with \`AbortSignal.any\`, replacing the manually-scoped abort forwarder and the prior \`outerController\` that bridged the construction-time \`abortSignal\` to inner connections. Same observable behaviour for the supersede / reset paths; new behaviour for the caller-signal path (surfaces as error rather than silently disconnecting).

Tests in \`@solana/subscribable\`, \`@solana/rpc-subscriptions-spec\`, and \`@solana/kit\` updated to exercise the new \`withSignal\` API. The "abort signal" describe blocks (which tested the deprecated construction-time signal) are replaced with focused \`withSignal()\` blocks covering the per-connection timeout, kill-switch, and supersede-doesn't-touch-caller-signal cases.
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.
…types`

Adds `UnwrapRpcResponse<T>` (a conditional type that unwraps `SolanaRpcResponse<U> → U` and passes non-envelope types through) and `isSolanaRpcResponse()` (a runtime type guard) to `@solana/rpc-types`.

The guard validates the envelope shape by duck-typing `context.slot: bigint` and the presence of `value` — only the load-bearing fields, so adding new envelope fields like `apiVersion` in the future doesn't ripple through the guard's contract. The narrowed type is `SolanaRpcResponse<UnwrapRpcResponse<T>>`, inferring the inner type from `T` directly so callers don't need a second generic parameter.

Callers that previously needed to lift `slot` from `context` can branch on the guard and access whatever fields they need.
`useSubscription(source)` opens a stream-store subscription on mount, re-opens whenever the source identity changes, and tears it down on unmount. The source is any `ReactiveStreamSource<T>` — `PendingRpcSubscriptionsRequest` is the canonical implementation. Pass `null` to disable.

The hook mirrors `useRequest`'s structure exactly: construct the lazy store via `useMemo`, fire `store.connect()` in a `useEffect`, tear down via `store.reset()` in cleanup. Same StrictMode-safe lifecycle pattern (mount → cleanup aborts → mount re-fires), same vocabulary, same per-call signal API.

The hook returns `{ data, error, reconnect, slot, status }`. Status is one of `loading | loaded | error | disabled`. After a notification arrives, an error-channel publish transitions to `error` while preserving the stale `data`; `reconnect()` returns to `loading` (preserving stale `data` and `error` for stale-while-revalidate) before settling on `loaded` or a fresh `error`. The bridge maps the store's `idle` state to `loading` when enabled (matching the about-to-commit-effect render) and to `disabled` when the source is null.

Notifications shaped as `SolanaRpcResponse<U>` (account/program/signature) are unwrapped via `isSolanaRpcResponse` from `@solana/rpc-types`: `data` is the inner value `U` and `slot` is lifted from `context.slot`. Raw notifications (slot/logs/root) pass through with `slot: undefined`. The `UnwrapRpcResponse<T>` conditional type (also from `@solana/rpc-types`) tracks the runtime unwrap at the type level.

Optional `getAbortSignal: () => AbortSignal` is invoked on every connection (initial subscribe + every `reconnect()`). The returned signal is composed with the store's per-connection controller via `AbortSignal.any` through `withSignal(signal).connect()`. The natural use is per-connection timeouts (`() => AbortSignal.timeout(30_000)`), which reset on reconnect. Factory is ref-synced — inline closures are fine. `reconnect()` accepts an optional `{ abortSignal }` override for one specific attempt; presence-based semantics distinguish "use factory" (omit the key), "explicit signal" (`{ abortSignal: signal }`), and "no signal" (`{ abortSignal: undefined }`).

SSR-safe: on the server the connect effect doesn't run, so the store stays `idle` and the hook reports `status: 'loading'`. The first client render hydrates from the same paint and commits the connect.

Adds `disabledStreamStore<T>()` to `staticStores.ts` — the stream-store analogue of `disabledActionStore` for the null-source case. Pinned by new tests alongside the existing `disabledActionStore` invariants.

Exports `SubscriptionResult<T>` and `UseSubscriptionOptions` for plugin hooks to build on.
…rt `@solana/subscribable` from kit

Switches `@solana/react` from pinning each consumed Kit sub-package individually to a single `@solana/kit` peer dependency. Promoting Kit to a peer means apps that depend on both `@solana/react` and `@solana/kit` get one deduplicated Kit instance — important for anything relying on shared types and `instanceof SolanaError` checks — and lets consumers upgrade Kit independently of React within the peer range. Mirrors the convention used by Solana program clients.

To support a single import root, this branch also adds `export * from '@solana/subscribable'` to the `@solana/kit` barrel. The reactive store types (`ReactiveStreamSource`, `ReactiveStreamStore`, `ReactiveActionSource`, etc.) and helpers (`createReactiveActionStore`, `createReactiveStoreFromDataPublisherFactory`) were already needed by anyone consuming Kit's `PendingRpcSubscriptionsRequest.reactiveStore()` — promoting them onto the Kit surface formalises that.

Every `import { … } from '@solana/subscribable'` (and the remaining sub-package imports) in `packages/react/src/**` is rewritten to `from '@solana/kit'`. No behaviour change beyond the dep-tree shape; `@solana/kit` itself depends on the same sub-packages.

`@solana/promises` stays a direct dep — it's a small utility not part of Kit's public surface.
…store config

Add `RpcSendable<T>` to `@solana/rpc-spec` and `RpcSubscribable<T>` to `@solana/rpc-subscriptions-spec` — structural duck-types covering just `send({ abortSignal })` and `subscribe({ abortSignal })` respectively. Both are intentionally narrower than the concrete `PendingRpcRequest<T>` / `PendingRpcSubscriptionsRequest<T>`, which also expose `reactiveStore()`. Use them at consumer boundaries to accept request-like objects without forcing producers to implement the full store-bearing shape.

Loosens the `rpcRequest` and `rpcSubscriptionRequest` fields on `CreateReactiveStoreWithInitialValueAndSlotTrackingConfig` (exported) and the internal config for `createAsyncGeneratorWithInitialValueAndSlotTracking` to use the new types. Both primitives only ever call `.send()` and `.subscribe()` on their inputs, so the previous `Pending*Request` constraint was over-tight — existing callers passing concrete `rpc.getAccountInfo(addr)` / `rpcSubscriptions.accountNotifications(addr)` results still satisfy the loosened types structurally.

Motivation: plugin-authored wrappers (caching layers, request batchers, request dedup) and test mocks no longer need to stub out an unused `reactiveStore()` method just to satisfy the type. Parallels the existing `ReactiveStreamSource<T>` / `ReactiveActionSource<T>` duck-types in `@solana/subscribable` — same pattern, applied to the producer side of one-shot and subscription requests.
`useTrackedData(spec)` renders reactive state for an RPC subscription seeded by a one-shot RPC fetch, slot-deduped. The subscription (e.g. `accountNotifications`) is the primary source of live updates; the initial fetch (e.g. `getBalance`, `getAccountInfo`) provides a value to surface as soon as it resolves — typically before the first subscription notification arrives — so the `loading` paint is shorter than subscription-only would give you. The underlying store slot-dedupes between the two sources: out-of-order arrivals never regress the surfaced value.

The hook is built on `createReactiveStoreWithInitialValueAndSlotTracking` from `@solana/kit` — the slot tracking, abort plumbing, and stale-while-revalidate behaviour live one layer down. The React surface reduces to `useSyncExternalStore` glue plus the per-attempt signal API. The Kit primitive's config type is re-shaped as `TrackedDataSpec<TRpcValue, TSubscriptionValue, TItem>` for friendlier use-site naming; the two are mutually assignable.

Returns `{ data, slot, error, refresh, status }` where `status` is one of `loading | loaded | error | disabled`. `slot` is lifted from the underlying `SolanaRpcResponse` envelope so consumers can show "data as of slot X" UIs. After a notification arrives, an error transitions to `status: 'error'` while preserving the stale `data` and `slot`; `refresh()` re-runs both the initial RPC and the subscription, returns `status` to `loading` (preserving stale `data` and `error` for stale-while-revalidate), and settles on `loaded` or a fresh `error`. Pass `null` for the spec to gate the work off — the result reports `status: 'disabled'`. Memoize the spec with `useMemo` keyed on its inputs — stable identity is how the hook knows when to tear down and re-run.

Optional `getAbortSignal: () => AbortSignal` is invoked on every attempt (initial run + every `refresh()`). The returned signal is composed with the store's per-attempt controller via `AbortSignal.any` through `withSignal(signal).connect()`. The natural use is per-attempt timeouts (`() => AbortSignal.timeout(30_000)`), which reset on refresh. Factory is ref-synced — inline closures are fine. `refresh()` accepts an optional `{ abortSignal }` override for one specific attempt; presence-based semantics distinguish "use factory" (omit the key), "explicit signal" (`{ abortSignal: signal }`), and "no signal" (`{ abortSignal: undefined }`).

SSR-safe: on the server the connect effect doesn't run, so the store stays `idle` and the hook reports `status: 'loading'`. The first client render hydrates from the same paint and commits the connect.

Adds an internal `useTrackedDataResult` bridge — lighter than `useSubscriptionResult` because the envelope shape is fixed at the type level (the kit primitive always emits `SolanaRpcResponse<TItem>`), so `data` and `slot` come straight off the unwrapped envelope, no runtime duck-type detection.

Exports `TrackedDataResult<T>`, `TrackedDataSpec<TRpc, TSub, T>`, and `UseTrackedDataOptions` alongside the hook so plugin hooks built on top can declare their return shape against them.
Adds an opt-in `@solana/react/swr` subpath that bridges Kit's reactive primitives into SWR's cache. Hooks under this adapter carry the `Swr` suffix to keep the cache backing visible at the call site, mirroring the spec's naming convention.

`useRequestSwr(key, source, options?)` is the SWR-backed counterpart to `useRequest`. Accepts the same source shape — a `ReactiveActionSource<T>` (satisfied by `PendingRpcRequest<T>`) or a `(signal: AbortSignal) => Promise<T>` function — and routes it through SWR so components reading the same key share one in-flight fetch and participate in SWR's revalidation, persistence, devtools, and Suspense semantics. Returns SWR's native `SWRResponse<T>` (not a Kit-flavored shape) so SWR-savvy users see the API they expect.

Either `key === null` or `source === null` disables the fetch. The hook coerces key to null when source is null so the two halves can be gated independently — useful when one of the source's inputs isn't yet known. Mirrors `useRequest`'s nullable-source pattern.

Options merge SWR's `SWRConfiguration` with the Kit-only `getAbortSignal: () => AbortSignal` option imported from `useRequest` — same factory pattern. Invoked per attempt; the returned signal is threaded into the source (`fn(signal)` for function sources, `withSignal(signal).dispatchAsync()` for `ReactiveActionSource`). Primary use is per-attempt timeouts. SWR doesn't know about the abort itself — only the source does — but the source's rejection surfaces via `result.error`. To re-fire on demand, use SWR's native `result.mutate()` (no Kit-specific `refresh` is exposed; SWR owns that verb).

Subpath plumbing follows the existing `@solana/kit/program-client-core` pattern: the `getBaseConfig.ts` tsup helper special-cases `@solana/react` to emit a `swr.*` bundle per platform, `tsconfig.declarations.json` includes the new entry, and `package.json` exports the subpath alongside the main entry with a parallel browser remap. `swr@^2` is an optional peer dependency — consumers who don't use the subpath aren't required to install it.
SWR-backed counterpart to `useSubscription`. Routes a `ReactiveStreamSource<T>` through SWR's `useSWRSubscription` so multiple components reading the same key share one underlying connection and participate in SWR's cache and devtools.

Returns SWR's native `{ data, error }` shape. `data` is a `SlotTaggedValue<T>` — a flat `{ slot, value }` wrapper that mirrors core `useSubscription`'s top-level `{ data, slot }` fields, packaged into the single `data` slot that SWR subscriptions surface. `data.value` is the unwrapped notification (`SolanaRpcResponse` envelopes are decomposed via `isSolanaRpcResponse`); `data.slot` is lifted from `context.slot` or `undefined` for raw, non-envelope notifications.

Either `key === null` or `source === null` disables the subscription, mirroring `useSubscription`'s nullable-source pattern. The Kit-only `getAbortSignal` option is imported from `useSubscription` and threaded into the source's `withSignal(signal).connect()` for per-connection signals (typically timeouts).

`SlotTaggedValue<T>` is exported from this module and re-used by the upcoming `useTrackedDataSwr` — both SWR hooks present the same flat decomposition so a switch between them is just a different generic parameter, not a different access pattern.
@changeset-bot

changeset-bot Bot commented May 28, 2026

Copy link
Copy Markdown

🦋 Changeset detected

Latest commit: 395ad3f

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

This PR includes changesets to release 47 packages
Name Type
@solana/react Major
@solana/accounts 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/kit Major
@solana/nominal-types Major
@solana/offchain-messages Major
@solana/options Major
@solana/plugin-core Major
@solana/plugin-interfaces Major
@solana/program-client-core Major
@solana/programs Major
@solana/promises Major
@solana/rpc-api Major
@solana/rpc-graphql Major
@solana/rpc-parsed-types Major
@solana/rpc-spec-types Major
@solana/rpc-spec Major
@solana/rpc-subscriptions-api Major
@solana/rpc-subscriptions-channel-websocket Major
@solana/rpc-subscriptions-spec Major
@solana/rpc-subscriptions Major
@solana/rpc-transformers Major
@solana/rpc-transport-http Major
@solana/rpc-types Major
@solana/rpc Major
@solana/signers Major
@solana/subscribable Major
@solana/sysvars Major
@solana/transaction-confirmation 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 commented May 28, 2026

Copy link
Copy Markdown
Member Author

Warning

This pull request is not mergeable via GitHub because a downstack PR is open. Once all requirements are satisfied, merge this PR as a stack on Graphite.
Learn more

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

@bundlemon

bundlemon Bot commented May 28, 2026

Copy link
Copy Markdown

BundleMon

Files updated (28)
Status Path Size Limits
react/dist/index.browser.mjs
4.53KB (+1.43KB +46.23%) -
react/dist/index.native.mjs
4.52KB (+1.43KB +46.24%) -
react/dist/index.node.mjs
4.52KB (+1.43KB +46.26%) -
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-types/dist/index.browser.mjs
1.9KB (+102B +5.53%) -
rpc-types/dist/index.native.mjs
1.9KB (+101B +5.48%) -
rpc-types/dist/index.node.mjs
1.9KB (+101B +5.48%) -
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-transport-http/dist/index.browser.mjs
1.91KB (+16B +0.83%) -
rpc-transport-http/dist/index.native.mjs
1.9KB (+16B +0.83%) -
rpc-transport-http/dist/index.node.mjs
1.72KB (+16B +0.92%) -
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%) -
@solana/kit production bundle
kit/dist/index.production.min.js
52.55KB (-53B -0.1%) -
kit/dist/index.node.mjs
4.13KB (-345B -7.54%) -
kit/dist/index.browser.mjs
4.13KB (-346B -7.56%) -
kit/dist/index.native.mjs
4.13KB (-346B -7.56%) -
Unchanged files (119)
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/dist/index.native.mjs
1.81KB -
rpc/dist/index.browser.mjs
1.8KB -
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 +4.13KB +0.78%

Final result: ✅

View report in BundleMon website ➡️


Current branch size history | Target branch size history

@github-actions

Copy link
Copy Markdown
Contributor

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

@mcintyre94 mcintyre94 force-pushed the react/use-request-swr branch 6 times, most recently from 25d40de to 773481f Compare June 3, 2026 09:14
@mcintyre94 mcintyre94 closed this Jun 3, 2026
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