Skip to content

Add UnwrapRpcResponse<T> + splitSolanaRpcResponse() to @solana/rpc-types#1678

Draft
mcintyre94 wants to merge 1 commit into
reactive-store/preserve-retrying-errorfrom
rpc-types/unwrap-rpc-response
Draft

Add UnwrapRpcResponse<T> + splitSolanaRpcResponse() to @solana/rpc-types#1678
mcintyre94 wants to merge 1 commit into
reactive-store/preserve-retrying-errorfrom
rpc-types/unwrap-rpc-response

Conversation

@mcintyre94
Copy link
Copy Markdown
Member

@mcintyre94 mcintyre94 commented May 20, 2026

Summary of Changes

This PR adds a new type UnwrapRpcResponse which unwraps SolanaRpcResponse<T> to T and leaves any other type unchanged. In practice this allows extracting the inner value type from an input that may or may not be a SolanaRpcResponse.

Alongside this it adds a new function splitSolanaRpcResponse which takes an input that may be a SolanaRpcResponse and unwraps it to {value, slot: Slot}. If the input is not SolanaRpcResponse then it is returned as value with slot: undefined. The input can be undefined to allow bridging from a store with optional data.

These helpers are intended for processing RPC subscriptions in a react hook, but they apply to RPC requests with the SolanaRpcResponse wrapper too.

@changeset-bot
Copy link
Copy Markdown

changeset-bot Bot commented May 20, 2026

🦋 Changeset detected

Latest commit: b80e471

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

This PR includes changesets to release 47 packages
Name Type
@solana/rpc-types Minor
@solana/accounts Minor
@solana/kit Minor
@solana/plugin-interfaces Minor
@solana/react Minor
@solana/rpc-api Minor
@solana/rpc-graphql Minor
@solana/rpc-parsed-types Minor
@solana/rpc-subscriptions-api Minor
@solana/rpc-subscriptions Minor
@solana/rpc-transformers Minor
@solana/rpc Minor
@solana/signers Minor
@solana/sysvars Minor
@solana/transaction-confirmation Minor
@solana/transaction-messages Minor
@solana/transactions Minor
@solana/program-client-core Minor
@solana/instruction-plans Minor
@solana/programs Minor
@solana/wallet-account-signer Minor
@solana/compat 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/errors Minor
@solana/fast-stable-stringify Minor
@solana/fixed-points Minor
@solana/functional Minor
@solana/instructions Minor
@solana/keys Minor
@solana/nominal-types Minor
@solana/offchain-messages Minor
@solana/options Minor
@solana/plugin-core Minor
@solana/promises Minor
@solana/rpc-spec-types Minor
@solana/rpc-spec Minor
@solana/rpc-subscriptions-channel-websocket Minor
@solana/rpc-subscriptions-spec Minor
@solana/rpc-transport-http Minor
@solana/subscribable 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

@bundlemon
Copy link
Copy Markdown

bundlemon Bot commented May 20, 2026

BundleMon

Files updated (25)
Status Path Size Limits
react/dist/index.native.mjs
4.33KB (+1.24KB +40.01%) -
react/dist/index.browser.mjs
4.33KB (+1.24KB +39.95%) -
react/dist/index.node.mjs
4.33KB (+1.24KB +39.99%) -
@solana/kit production bundle
kit/dist/index.production.min.js
52.47KB (+209B +0.39%) -
errors/dist/index.browser.mjs
20.69KB (+170B +0.81%) -
errors/dist/index.native.mjs
20.69KB (+170B +0.81%) -
errors/dist/index.node.mjs
20.71KB (+170B +0.81%) -
wallet-account-signer/dist/index.native.mjs
17.53KB (+165B +0.93%) -
wallet-account-signer/dist/index.browser.mjs
17.53KB (+164B +0.92%) -
wallet-account-signer/dist/index.node.mjs
17.54KB (+164B +0.92%) -
kit/dist/index.native.mjs
4.13KB (+161B +3.96%) -
kit/dist/index.node.mjs
4.13KB (+161B +3.96%) -
kit/dist/index.browser.mjs
4.13KB (+159B +3.91%) -
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.92KB (+126B +6.83%) -
rpc-types/dist/index.native.mjs
1.92KB (+126B +6.84%) -
rpc-types/dist/index.node.mjs
1.92KB (+125B +6.79%) -
rpc-subscriptions-spec/dist/index.browser.mjs
2.19KB (-13B -0.58%) -
rpc-subscriptions-spec/dist/index.native.mjs
2.19KB (-13B -0.58%) -
rpc-subscriptions-spec/dist/index.node.mjs
2.23KB (-13B -0.56%) -
rpc-spec/dist/index.browser.mjs
898B (-20B -2.18%) -
rpc-spec/dist/index.native.mjs
897B (-21B -2.29%) -
rpc-spec/dist/index.node.mjs
896B (-21B -2.29%) -
Unchanged files (122)
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/dist/index.browser.mjs
1.8KB -
rpc-transport-http/dist/index.node.mjs
1.72KB -
rpc-subscriptions-channel-websocket/dist/inde
x.node.mjs
1.33KB -
rpc-subscriptions-channel-websocket/dist/inde
x.native.mjs
1.27KB -
rpc-subscriptions-channel-websocket/dist/inde
x.browser.mjs
1.26KB -
program-client-core/dist/index.browser.mjs
1.21KB -
program-client-core/dist/index.native.mjs
1.21KB -
program-client-core/dist/index.node.mjs
1.21KB -
options/dist/index.browser.mjs
1.18KB -
options/dist/index.native.mjs
1.18KB -
options/dist/index.node.mjs
1.17KB -
accounts/dist/index.browser.mjs
1.17KB -
accounts/dist/index.native.mjs
1.17KB -
accounts/dist/index.node.mjs
1.16KB -
rpc-api/dist/index.browser.mjs
998B -
rpc-api/dist/index.native.mjs
997B -
rpc-api/dist/index.node.mjs
995B -
compat/dist/index.browser.mjs
969B -
compat/dist/index.native.mjs
968B -
compat/dist/index.node.mjs
966B -
rpc-spec-types/dist/index.browser.mjs
962B -
rpc-spec-types/dist/index.native.mjs
961B -
rpc-spec-types/dist/index.node.mjs
959B -
rpc-subscriptions-api/dist/index.native.mjs
870B -
rpc-subscriptions-api/dist/index.node.mjs
869B -
rpc-subscriptions-api/dist/index.browser.mjs
868B -
promises/dist/index.native.mjs
841B -
promises/dist/index.node.mjs
840B -
promises/dist/index.browser.mjs
839B -
plugin-core/dist/index.browser.mjs
820B -
plugin-core/dist/index.native.mjs
819B -
plugin-core/dist/index.node.mjs
817B -
assertions/dist/index.browser.mjs
783B -
instructions/dist/index.browser.mjs
771B -
instructions/dist/index.native.mjs
770B -
instructions/dist/index.node.mjs
768B -
fast-stable-stringify/dist/index.browser.mjs
726B -
fast-stable-stringify/dist/index.native.mjs
725B -
assertions/dist/index.native.mjs
724B -
fast-stable-stringify/dist/index.node.mjs
724B -
assertions/dist/index.node.mjs
723B -
programs/dist/index.browser.mjs
329B -
programs/dist/index.native.mjs
327B -
programs/dist/index.node.mjs
325B -
fs-impl/dist/index.browser.mjs
245B -
event-target-impl/dist/index.node.mjs
230B -
functional/dist/index.browser.mjs
154B -
functional/dist/index.native.mjs
152B -
text-encoding-impl/dist/index.native.mjs
152B -
functional/dist/index.node.mjs
151B -
codecs/dist/index.browser.mjs
145B -
codecs/dist/index.native.mjs
144B -
codecs/dist/index.node.mjs
142B -
event-target-impl/dist/index.browser.mjs
133B -
ws-impl/dist/index.node.mjs
131B -
text-encoding-impl/dist/index.browser.mjs
122B -
fs-impl/dist/index.node.mjs
120B -
text-encoding-impl/dist/index.node.mjs
119B -
ws-impl/dist/index.browser.mjs
113B -
crypto-impl/dist/index.node.mjs
111B -
crypto-impl/dist/index.browser.mjs
109B -
rpc-parsed-types/dist/index.browser.mjs
66B -
rpc-parsed-types/dist/index.native.mjs
65B -
rpc-parsed-types/dist/index.node.mjs
63B -

Total files change +6.05KB +1.15%

Final result: ✅

View report in BundleMon website ➡️


Current branch size history | Target branch size history

@mcintyre94 mcintyre94 force-pushed the rpc-types/unwrap-rpc-response branch from 4e03c8b to c11b360 Compare May 20, 2026 12:58
@github-actions
Copy link
Copy Markdown
Contributor

github-actions Bot commented May 20, 2026

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

…pc-types`

Adds a co-located type-level and runtime decomposition for the `SolanaRpcResponse<T>` envelope, so callers can handle notifications that may or may not be wrapped without re-implementing the duck-type check.

`UnwrapRpcResponse<T>` is a conditional type that unwraps `SolanaRpcResponse<U>` → `U` and passes through other shapes unchanged. `splitSolanaRpcResponse()` runtime-detects the envelope via duck-type (`'context' in x && 'value' in x`) and decomposes into the inner value and the lifted slot. Raw notifications pass through with `slot: undefined`; `undefined` input returns both halves as `undefined`. Four overloads narrow the return type to match the input: `SolanaRpcResponse<T>` → `{ slot: Slot; value: T }`; `undefined` → `{ slot: undefined; value: undefined }`; `SolanaRpcResponse<T> | undefined` → `{ slot: Slot | undefined; value: T | undefined }`; raw `T` → `{ slot: undefined; value: T }`. The third overload lets callers pipe a possibly-undefined source straight through without an external null-check.

T is intentionally unconstrained — the runtime check handles arbitrary shapes, and the overloads surface precise return types per call site. Tests cover the envelope path, raw passthrough, `undefined`/`null` inputs, malformed shapes (missing `context` or `value`), primitives, and envelopes whose `value` itself is `undefined` / `null`.
@mcintyre94 mcintyre94 force-pushed the rpc-types/unwrap-rpc-response branch from c11b360 to b80e471 Compare May 20, 2026 13:34
@mcintyre94 mcintyre94 force-pushed the reactive-store/preserve-retrying-error branch from efde42d to 2fd75aa Compare May 20, 2026 13:34
@mcintyre94
Copy link
Copy Markdown
Member Author

@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 helpers to @solana/rpc-types:

  • UnwrapRpcResponse<T> — conditional type that unwraps SolanaRpcResponse<U> to U, passes other types through.
  • splitSolanaRpcResponse() — runtime helper that duck-types the envelope shape ('context' in x && 'value' in x) and decomposes into { value, slot }. Raw inputs pass through with slot: undefined; undefined returns both halves as undefined.

Four overloads cover the envelope, undefined, SolanaRpcResponse<T> | undefined, and raw T cases. Comes with a thorough unit test file and a small type test, plus a minor changeset.

Things to watch out for / notes for subsequent reviewers

1. Missing README update. The package's README.md documents every existing public type and function but isn't updated here. Per the repo's TypeScript READMEs skill ("When adding a new public API, add or update the package's README"), this PR should add UnwrapRpcResponse under "Types" and splitSolanaRpcResponse under "Functions". The JSDoc is solid, but the README is the user-facing entry point.

2. Type-level gap for genuinely mixed unions. The PR description says these helpers handle "an input that may or may not be a SolanaRpcResponse". The overloads cover SolanaRpcResponse<T>, undefined, SolanaRpcResponse<T> | undefined, and raw T — but not SolanaRpcResponse<T> | T (i.e. the case where the call site genuinely doesn't know which it has). With a union like SolanaRpcResponse<X> | RawX, none of overloads 1–3 match (each arm fails them), so it falls through to overload 4 (notification: T) and you get { slot: undefined; value: SolanaRpcResponse<X> | RawX } — slot is statically undefined even though at runtime the envelope arm would produce a real slot.

For the React-hook use case (a single subscription whose envelope-ness is statically known) this is fine. But if the intent is broader, consider adding an overload before overload 4 along the lines of:

export function splitSolanaRpcResponse<TRaw>(
    notification: SolanaRpcResponse<TRaw> | TRaw
): { slot: Slot | undefined; value: TRaw };

A matching type-test case would also be worth adding so the resolution doesn't silently regress.

3. Overload ordering is correct, but fragile. Overload 4 (<T>(notification: T)) is broad enough to swallow envelope inputs if TS ever picked it. It works today because earlier overloads come first and TS picks the first matching signature, but it's worth a comment explaining the ordering so a future contributor doesn't reorder them. Adding the union overload above would push overload 4 even further down — also fine, just keep it as the final fallback.

4. Minor: null handling is implicit. The runtime guard (notification != null) accepts null, but the public overloads don't list it; the test for null casts via as unknown as undefined. Either extend the undefined overload to null | undefined and add a real null overload, or have the test cover only what the type contract promises. The current state is slightly misleading.

5. Naming. splitSolanaRpcResponse is descriptive, but the function also unwraps — unwrapSolanaRpcResponse would arguably read better as a callsite verb (const { value, slot } = unwrapSolanaRpcResponse(...)). Subjective, not blocking.

Nothing here is a correctness issue — the runtime behaviour and tests are tight. Main asks are the README update and a decision on whether the mixed-union case is in scope.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants