Skip to content

Commit 1d12a42

Browse files
committed
Document @solana/rpc-subscriptions-spec with TypeDoc
1 parent 24d33eb commit 1d12a42

9 files changed

Lines changed: 199 additions & 3 deletions

File tree

‎packages/rpc-spec/src/rpc.ts‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -52,7 +52,7 @@ type PendingRpcRequestReturnTypeMapper<TMethodImplementation> =
5252
: never;
5353

5454
/**
55-
* Creates a {@link Rpc} instance given an {@link RpcApi | RpcApi<TRpcMethods>} and a
55+
* Creates a {@link Rpc} instance given a {@link RpcApi | RpcApi<TRpcMethods>} and a
5656
* {@link RpcTransport} capable of fulfilling them.
5757
*/
5858
export function createRpc<TRpcMethods, TRpcTransport extends RpcTransport>(

‎packages/rpc-subscriptions-spec/README.md‎

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ This API is designed to be used as follows:
1717

1818
```ts
1919
const rpcSubscriptions =
20-
// Step 1 - Create an `RpcSubscriptions` instance. This may be stateful.
20+
// Step 1 - Create a `RpcSubscriptions` instance. This may be stateful.
2121
createSolanaRpcSubscriptions(mainnet('wss://api.mainnet-beta.solana.com'));
2222
const response = await rpcSubscriptions
2323
// Step 2 - Call supported methods on it to produce `PendingRpcSubscriptionsRequest` objects.
@@ -40,7 +40,7 @@ try {
4040

4141
### `RpcSubscriptionsChannel<TOutboundMessage, TInboundMessage>`
4242

43-
A channel is a `DataPublisher` that you can subscribe to events of type `RpcSubscriptionChannelEvents<TInboundMessage>`. Additionally, you can use it to send messages of type `TOutboundMessage` back to the remote end by calling the `send(message)` method.
43+
A channel is a `DataPublisher` on which you can subscribe to events of type `RpcSubscriptionChannelEvents<TInboundMessage>`. Additionally, you can use this object to send messages of type `TOutboundMessage` back to the remote end by calling its `send(message)` method.
4444

4545
### `RpcSubscriptionsChannelCreator<TOutboundMessage, TInboundMessage>`
4646

‎packages/rpc-subscriptions-spec/src/index.ts‎

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,32 @@
1+
/**
2+
* This package contains types that describe the implementation of the JSON RPC Subscriptions API,
3+
* as well as methods to create one. It can be used standalone, but it is also exported as part of
4+
* Kit [`@solana/kit`](https://github.com/anza-xyz/kit/tree/main/packages/kit).
5+
*
6+
* @example
7+
* ```ts
8+
* const rpcSubscriptions =
9+
* // Step 1 - Create a `RpcSubscriptions` instance. This may be stateful.
10+
* createSolanaRpcSubscriptions(mainnet('wss://api.mainnet-beta.solana.com'));
11+
* const response = await rpcSubscriptions
12+
* // Step 2 - Call supported methods on it to produce `PendingRpcSubscriptionsRequest` objects.
13+
* .slotNotifications({ commitment: 'confirmed' })
14+
* // Step 3 - Call the `subscribe()` method on those pending requests to trigger them.
15+
* .subscribe({ abortSignal: AbortSignal.timeout(10_000) });
16+
* // Step 4 - Iterate over the result.
17+
* try {
18+
* for await (const slotNotification of slotNotifications) {
19+
* console.log('Got a slot notification', slotNotification);
20+
* }
21+
* } catch (e) {
22+
* console.error('The subscription closed unexpectedly', e);
23+
* } finally {
24+
* console.log('We have stopped listening for notifications');
25+
* }
26+
* ```
27+
*
28+
* @packageDocumentation
29+
*/
130
export * from './rpc-subscriptions-request';
231
export * from './rpc-subscriptions';
332
export * from './rpc-subscriptions-api';

‎packages/rpc-subscriptions-spec/src/rpc-subscriptions-api.ts‎

Lines changed: 81 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,9 +6,23 @@ import { RpcSubscriptionsTransportDataEvents } from './rpc-subscriptions-transpo
66

77
export type RpcSubscriptionsApiConfig<TApiMethods extends RpcSubscriptionsApiMethods> = Readonly<{
88
planExecutor: RpcSubscriptionsPlanExecutor<ReturnType<TApiMethods[keyof TApiMethods]>>;
9+
/**
10+
* An optional function that transforms the {@link RpcRequest} before it is sent to the JSON RPC
11+
* server.
12+
*
13+
* This is useful when the params supplied by the caller need to be transformed before
14+
* forwarding the message to the server. Use cases for this include applying defaults,
15+
* forwarding calls to renamed methods, and serializing complex values.
16+
*/
917
requestTransformer?: RpcRequestTransformer;
1018
}>;
1119

20+
/**
21+
* A function that implements a protocol for subscribing and unsubscribing from notifications given
22+
* a {@link RpcSubscriptionsChannel}, a {@link RpcRequest}, and an `AbortSignal`.
23+
*
24+
* @returns A {@link DataPublisher} that emits {@link RpcSubscriptionsTransportDataEvents}
25+
*/
1226
type RpcSubscriptionsPlanExecutor<TNotification> = (
1327
config: Readonly<{
1428
channel: RpcSubscriptionsChannel<unknown, unknown>;
@@ -17,6 +31,26 @@ type RpcSubscriptionsPlanExecutor<TNotification> = (
1731
}>,
1832
) => Promise<DataPublisher<RpcSubscriptionsTransportDataEvents<TNotification>>>;
1933

34+
/**
35+
* This type allows an {@link RpcSubscriptionsApi} to describe how a particular subscription should
36+
* be issued to the JSON RPC server.
37+
*
38+
* Given a function that was called on a {@link RpcSubscriptions}, this object exposes an `execute`
39+
* function that dictates which subscription request will be sent, how the underlying transport will
40+
* be used, and how the notifications will be transformed.
41+
*
42+
* This function accepts a {@link RpcSubscriptionsChannel} and an `AbortSignal` and asynchronously
43+
* returns a {@link DataPublisher}. This gives us the opportunity to:
44+
*
45+
* - define the `payload` from the requested method name and parameters before passing it to the
46+
* channel.
47+
* - call the underlying channel zero, one or multiple times depending on the use-case (e.g.
48+
* caching or coalescing multiple subscriptions).
49+
* - transform the notification from the JSON RPC server, in case it does not match the
50+
* `TNotification` specified by the
51+
* {@link PendingRpcSubscriptionsRequest | PendingRpcSubscriptionsRequest<TNotification>} emitted
52+
* from the publisher returned.
53+
*/
2054
export type RpcSubscriptionsPlan<TNotification> = Readonly<{
2155
/**
2256
* This method may be called with a newly-opened channel or a pre-established channel.
@@ -35,6 +69,12 @@ export type RpcSubscriptionsPlan<TNotification> = Readonly<{
3569
request: RpcRequest;
3670
}>;
3771

72+
/**
73+
* For each of `TRpcSubscriptionsMethods`, this object exposes a method with the same name that maps
74+
* between its input arguments and a
75+
* {@link RpcSubscriptionsPlan | RpcSubscriptionsPlan<TNotification>} that implements the execution
76+
* of a JSON RPC subscription for `TNotifications`.
77+
*/
3878
export type RpcSubscriptionsApi<TRpcSubscriptionMethods> = {
3979
[MethodName in keyof TRpcSubscriptionMethods]: RpcSubscriptionsReturnTypeMapper<
4080
TRpcSubscriptionMethods[MethodName]
@@ -51,6 +91,47 @@ export interface RpcSubscriptionsApiMethods {
5191
[methodName: string]: RpcSubscriptionsApiMethod;
5292
}
5393

94+
/**
95+
* Creates a JavaScript proxy that converts _any_ function call called on it to a
96+
* {@link RpcSubscriptionsPlan} by creating an `execute` function that:
97+
*
98+
* - calls the supplied {@link RpcSubscriptionsApiConfig.planExecutor} with a JSON RPC v2 payload
99+
* object with the requested `methodName` and `params` properties, optionally transformed by
100+
* {@link RpcSubscriptionsApiConfig.requestTransformer}.
101+
*
102+
* @example
103+
* ```ts
104+
* // For example, given this `RpcSubscriptionsApi`:
105+
* const rpcSubscriptionsApi = createJsonRpcSubscriptionsApi({
106+
* async planExecutor({ channel, request }) {
107+
* await channel.send(request);
108+
* return {
109+
* ...channel,
110+
* on(type, listener, options) {
111+
* if (type !== 'message') {
112+
* return channel.on(type, listener, options);
113+
* }
114+
* return channel.on(
115+
* 'message',
116+
* function resultGettingListener(message) {
117+
* listener(message.result);
118+
* },
119+
* options,
120+
* );
121+
* }
122+
* }
123+
* },
124+
* requestTransformer: (...rawParams) => rawParams.reverse(),
125+
* });
126+
*
127+
* // ...the following function call:
128+
* rpcSubscriptionsApi.foo('bar', { baz: 'bat' });
129+
*
130+
* // ...will produce a `RpcSubscriptionsPlan` that:
131+
* // - Uses the following payload: { id: 1, jsonrpc: '2.0', method: 'foo', params: [{ baz: 'bat' }, 'bar'] }.
132+
* // - Emits the "result" property of each RPC Subscriptions message.
133+
* ```
134+
*/
54135
export function createRpcSubscriptionsApi<TRpcSubscriptionsApiMethods extends RpcSubscriptionsApiMethods>(
55136
config: RpcSubscriptionsApiConfig<TRpcSubscriptionsApiMethods>,
56137
): RpcSubscriptionsApi<TRpcSubscriptionsApiMethods> {

‎packages/rpc-subscriptions-spec/src/rpc-subscriptions-channel.ts‎

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,21 +12,51 @@ type RpcSubscriptionsChannelSolanaErrorCode =
1212
| typeof SOLANA_ERROR__RPC_SUBSCRIPTIONS__CHANNEL_FAILED_TO_CONNECT;
1313

1414
export type RpcSubscriptionChannelEvents<TInboundMessage> = {
15+
/**
16+
* Fires when the channel closes unexpectedly.
17+
* @eventProperty
18+
*/
1519
error: SolanaError<RpcSubscriptionsChannelSolanaErrorCode>;
20+
/**
21+
* Fires on every message received from the remote end.
22+
* @eventProperty
23+
*/
1624
message: TInboundMessage;
1725
};
1826

27+
/**
28+
* A {@link DataPublisher} on which you can subscribe to events of type
29+
* {@link RpcSubscriptionChannelEvents | RpcSubscriptionChannelEvents<TInboundMessage>}.
30+
* Additionally, you can use this object to send messages of type `TOutboundMessage` back to the
31+
* remote end by calling its {@link RpcSubscriptionsChannel.send | `send(message)`} method.
32+
*/
1933
export interface RpcSubscriptionsChannel<TOutboundMessage, TInboundMessage>
2034
extends DataPublisher<RpcSubscriptionChannelEvents<TInboundMessage>> {
2135
send(message: TOutboundMessage): Promise<void>;
2236
}
2337

38+
/**
39+
* A channel creator is a function that accepts an `AbortSignal`, returns a new
40+
* {@link RpcSubscriptionsChannel}, and tears down the channel when the abort signal fires.
41+
*/
2442
export type RpcSubscriptionsChannelCreator<TOutboundMessage, TInboundMessage> = (
2543
config: Readonly<{
2644
abortSignal: AbortSignal;
2745
}>,
2846
) => Promise<RpcSubscriptionsChannel<TOutboundMessage, TInboundMessage>>;
2947

48+
/**
49+
* Given a channel with inbound messages of type `T` and a function of type `T => U`, returns a new
50+
* channel with inbound messages of type `U`.
51+
*
52+
* Note that this only affects messages of type `"message"` and thus, does not affect incoming error
53+
* messages.
54+
*
55+
* @example Parsing incoming JSON messages
56+
* ```ts
57+
* const transformedChannel = transformChannelInboundMessages(channel, JSON.parse);
58+
* ```
59+
*/
3060
export function transformChannelInboundMessages<TOutboundMessage, TNewInboundMessage, TInboundMessage>(
3161
channel: RpcSubscriptionsChannel<TOutboundMessage, TInboundMessage>,
3262
transform: (message: TInboundMessage) => TNewInboundMessage,
@@ -50,6 +80,15 @@ export function transformChannelInboundMessages<TOutboundMessage, TNewInboundMes
5080
});
5181
}
5282

83+
/**
84+
* Given a channel with outbound messages of type `T` and a function of type `U => T`, returns a new
85+
* channel with outbound messages of type `U`.
86+
*
87+
* @example Stringifying JSON messages before sending them over the wire
88+
* ```ts
89+
* const transformedChannel = transformChannelOutboundMessages(channel, JSON.stringify);
90+
* ```
91+
*/
5392
export function transformChannelOutboundMessages<TNewOutboundMessage, TOutboundMessage, TInboundMessage>(
5493
channel: RpcSubscriptionsChannel<TOutboundMessage, TInboundMessage>,
5594
transform: (message: TNewOutboundMessage) => TOutboundMessage,

‎packages/rpc-subscriptions-spec/src/rpc-subscriptions-pubsub-plan.ts‎

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -95,6 +95,18 @@ function getMemoizedDemultiplexedNotificationPublisherFromChannelAndResponseTran
9595
return publisher;
9696
}
9797

98+
/**
99+
* Given a channel, this function executes the particular subscription plan required by the Solana
100+
* JSON RPC Subscriptions API.
101+
*
102+
* @param config
103+
*
104+
* 1. Calls the `subscribeRequest` on the remote RPC
105+
* 2. Waits for a response containing the subscription id
106+
* 3. Returns a {@link DataPublisher} that publishes notifications related to that subscriptions id,
107+
* filtering out all others
108+
* 4. Calls the `unsubscribeMethodName` on the remote RPC when the abort signal is fired.
109+
*/
98110
export async function executeRpcPubSubSubscriptionPlan<TNotification>({
99111
channel,
100112
responseTransformer,
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,17 @@
1+
/**
2+
* Pending subscriptions are the result of calling a supported method on a {@link RpcSubscriptions}
3+
* object. They encapsulate all of the information necessary to make the subscription without
4+
* actually making it.
5+
*
6+
* Calling the {@link PendingRpcSubscriptionsRequest.subscribe | `subscribe(options)`} method on a
7+
* {@link PendingRpcSubscriptionsRequest | PendingRpcSubscriptionsRequest<TNotification>} will
8+
* trigger the subscription and return a promise for an async iterable that vends `TNotifications`.
9+
*/
110
export type PendingRpcSubscriptionsRequest<TNotification> = {
211
subscribe(options: RpcSubscribeOptions): Promise<AsyncIterable<TNotification>>;
312
};
413

514
export type RpcSubscribeOptions = Readonly<{
15+
/** An `AbortSignal` to fire when you want to unsubscribe */
616
abortSignal: AbortSignal;
717
}>;

‎packages/rpc-subscriptions-spec/src/rpc-subscriptions-transport.ts‎

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,14 +4,27 @@ import { DataPublisher } from '@solana/subscribable';
44
import { RpcSubscriptionsPlan } from './rpc-subscriptions-api';
55

66
export type RpcSubscriptionsTransportDataEvents<TNotification> = {
7+
/**
8+
* Fires when there is an error with the subscription or the channel.
9+
* @eventProperty
10+
*/
711
error: SolanaError;
12+
/**
13+
* Fires on every notification received.
14+
* @eventProperty
15+
*/
816
notification: TNotification;
917
};
1018

1119
interface RpcSubscriptionsTransportConfig<TNotification> extends RpcSubscriptionsPlan<TNotification> {
20+
/** An `AbortSignal` to fire when you want to unsubscribe */
1221
signal: AbortSignal;
1322
}
1423

24+
/**
25+
* A function that can act as a transport for a {@link RpcSubscriptions}. It need only return a
26+
* promise for a {@link DataPublisher} given the supplied config.
27+
*/
1528
export interface RpcSubscriptionsTransport {
1629
<TNotification>(
1730
config: RpcSubscriptionsTransportConfig<TNotification>,

‎packages/rpc-subscriptions-spec/src/rpc-subscriptions.ts‎

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,13 @@ export type RpcSubscriptionsConfig<TRpcMethods> = Readonly<{
1111
transport: RpcSubscriptionsTransport;
1212
}>;
1313

14+
/**
15+
* An object that exposes all of the functions described by `TRpcSubscriptionsMethods`.
16+
*
17+
* Calling each method returns a
18+
* {@link PendingRpcSubscriptionsRequest | PendingRpcSubscriptionsRequest<TNotification>} where
19+
* `TNotification` is that method's notification type.
20+
*/
1421
export type RpcSubscriptions<TRpcSubscriptionsMethods> = {
1522
[TMethodName in keyof TRpcSubscriptionsMethods]: PendingRpcSubscriptionsRequestBuilder<
1623
OverloadImplementations<TRpcSubscriptionsMethods, TMethodName>
@@ -33,6 +40,11 @@ type PendingRpcSubscriptionsRequestReturnTypeMapper<TSubscriptionMethodImplement
3340
) => PendingRpcSubscriptionsRequest<ReturnType<TSubscriptionMethodImplementation>>
3441
: never;
3542

43+
/**
44+
* Creates a {@link RpcSubscriptions} instance given a
45+
* {@link RpcSubscriptionsApi | RpcSubscriptionsApi<TRpcSubscriptionsApiMethods>} and a
46+
* {@link RpcSubscriptionsTransport} capable of fulfilling them.
47+
*/
3648
export function createSubscriptionRpc<TRpcSubscriptionsApiMethods>(
3749
rpcConfig: RpcSubscriptionsConfig<TRpcSubscriptionsApiMethods>,
3850
): RpcSubscriptions<TRpcSubscriptionsApiMethods> {

0 commit comments

Comments
 (0)