-
Notifications
You must be signed in to change notification settings - Fork 181
Add framework-agnostic source types for reactive bindings #1606
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,17 @@ | ||
| --- | ||
| '@solana/subscribable': minor | ||
| '@solana/kit': minor | ||
| --- | ||
|
|
||
| Add framework-agnostic source duck-types for reactive bindings. | ||
|
|
||
| `@solana/subscribable` now exports two new types: | ||
|
|
||
| - `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 let reactive-framework bindings consume a single duck-type instead of naming concrete producer types — and let plugin authors expose their own pending-request objects to those bindings without modification. | ||
|
|
||
| Both source types live in `@solana/subscribable` and are not re-exported from `@solana/kit`, matching the existing convention for their parent `ReactiveStreamStore` / `ReactiveActionStore` types — anyone consuming a source duck-type is already in the reactive-primitives layer and will already be importing the related store types from the same package. | ||
|
|
||
| `@solana/kit` now publicly exports the previously-private `CreateReactiveStoreWithInitialValueAndSlotTrackingConfig` type so non-React consumers (e.g. plugins) can declare function return shapes based on it without taking a dependency on `@solana/react`. |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,8 @@ | ||
| import type { ReactiveActionSource } from '@solana/subscribable'; | ||
|
|
||
| import { PendingRpcRequest } from '../rpc'; | ||
|
|
||
| // `PendingRpcRequest<T>` is structurally assignable to `ReactiveActionSource<T>` — the duck-type | ||
| // reactive-framework bindings consume so they don't have to name a concrete producer type. | ||
| null as unknown as PendingRpcRequest<number> satisfies ReactiveActionSource<number>; | ||
| null as unknown as PendingRpcRequest<{ foo: string }> satisfies ReactiveActionSource<{ foo: string }>; |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,11 @@ | ||
| import type { ReactiveStreamSource } from '@solana/subscribable'; | ||
|
|
||
| import { PendingRpcSubscriptionsRequest } from '../rpc-subscriptions-request'; | ||
|
|
||
| // `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>; | ||
| null as unknown as PendingRpcSubscriptionsRequest<{ foo: string }> satisfies ReactiveStreamSource<{ | ||
| foo: string; | ||
| }>; | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -131,6 +131,28 @@ export type ReactiveStreamStore<T> = { | |
| */ | ||
| export type ReactiveStore<T> = ReactiveStreamStore<T>; | ||
|
|
||
| /** | ||
| * 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. | ||
| * | ||
| * @typeParam T - The value type emitted by the resulting stream store. | ||
| * | ||
| * @example | ||
| * ```ts | ||
| * function bind<T>(source: ReactiveStreamSource<T>, abortSignal: AbortSignal) { | ||
| * return source.reactiveStore({ abortSignal }); | ||
| * } | ||
| * ``` | ||
| * | ||
| * @see {@link ReactiveStreamStore} | ||
| * @see {@link ReactiveActionSource} | ||
| */ | ||
| export type ReactiveStreamSource<T> = { | ||
| reactiveStore(options: { abortSignal: AbortSignal }): ReactiveStreamStore<T>; | ||
|
Comment on lines
+134
to
+153
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Per the repo's JSDoc convention (see /**
* ...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
Member
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Updated these |
||
| }; | ||
|
|
||
| /** | ||
| * Returns a {@link ReactiveStreamStore} given a data publisher. | ||
| * | ||
|
|
||
There was a problem hiding this comment.
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 toPendingRpcSubscriptionsRequest<T>. Something like:This pins down the "strictly broader" relationship and would catch a future change that accidentally tightens
ReactiveStreamSourceto require methods that only the concrete producer has. Not blocking — the existing assertion already covers the contract the bindings rely on.