@solana/rpc-subscriptions-spec
Version:
A generic implementation of JSON RPC Subscriptions using proxies
120 lines • 5.92 kB
TypeScript
import { Callable, RpcRequest, RpcRequestTransformer } from '@solana/rpc-spec-types';
import { DataPublisher } from '@solana/subscribable';
import { RpcSubscriptionsChannel } from './rpc-subscriptions-channel';
import { RpcSubscriptionsTransportDataEvents } from './rpc-subscriptions-transport';
export type RpcSubscriptionsApiConfig<TApiMethods extends RpcSubscriptionsApiMethods> = Readonly<{
planExecutor: RpcSubscriptionsPlanExecutor<ReturnType<TApiMethods[keyof TApiMethods]>>;
/**
* An optional function that transforms the {@link RpcRequest} before it is sent to the JSON RPC
* server.
*
* This is useful when the params supplied by the caller need to be transformed before
* forwarding the message to the server. Use cases for this include applying defaults,
* forwarding calls to renamed methods, and serializing complex values.
*/
requestTransformer?: RpcRequestTransformer;
}>;
/**
* A function that implements a protocol for subscribing and unsubscribing from notifications given
* a {@link RpcSubscriptionsChannel}, a {@link RpcRequest}, and an `AbortSignal`.
*
* @returns A {@link DataPublisher} that emits {@link RpcSubscriptionsTransportDataEvents}
*/
type RpcSubscriptionsPlanExecutor<TNotification> = (config: Readonly<{
channel: RpcSubscriptionsChannel<unknown, unknown>;
request: RpcRequest;
signal: AbortSignal;
}>) => Promise<DataPublisher<RpcSubscriptionsTransportDataEvents<TNotification>>>;
/**
* This type allows an {@link RpcSubscriptionsApi} to describe how a particular subscription should
* be issued to the JSON RPC server.
*
* Given a function that was called on a {@link RpcSubscriptions}, this object exposes an `execute`
* function that dictates which subscription request will be sent, how the underlying transport will
* be used, and how the notifications will be transformed.
*
* This function accepts a {@link RpcSubscriptionsChannel} and an `AbortSignal` and asynchronously
* returns a {@link DataPublisher}. This gives us the opportunity to:
*
* - define the `payload` from the requested method name and parameters before passing it to the
* channel.
* - call the underlying channel zero, one or multiple times depending on the use-case (e.g.
* caching or coalescing multiple subscriptions).
* - transform the notification from the JSON RPC server, in case it does not match the
* `TNotification` specified by the
* {@link PendingRpcSubscriptionsRequest | PendingRpcSubscriptionsRequest<TNotification>} emitted
* from the publisher returned.
*/
export type RpcSubscriptionsPlan<TNotification> = Readonly<{
/**
* This method may be called with a newly-opened channel or a pre-established channel.
*/
execute: (config: Readonly<{
channel: RpcSubscriptionsChannel<unknown, unknown>;
signal: AbortSignal;
}>) => Promise<DataPublisher<RpcSubscriptionsTransportDataEvents<TNotification>>>;
/**
* This request is used to uniquely identify the subscription.
* It typically comes from the method name and parameters of the subscription call,
* after potentially being transformed by the RPC Subscriptions API.
*/
request: RpcRequest;
}>;
/**
* For each of `TRpcSubscriptionsMethods`, this object exposes a method with the same name that maps
* between its input arguments and a
* {@link RpcSubscriptionsPlan | RpcSubscriptionsPlan<TNotification>} that implements the execution
* of a JSON RPC subscription for `TNotifications`.
*/
export type RpcSubscriptionsApi<TRpcSubscriptionMethods> = {
[MethodName in keyof TRpcSubscriptionMethods]: RpcSubscriptionsReturnTypeMapper<TRpcSubscriptionMethods[MethodName]>;
};
type RpcSubscriptionsReturnTypeMapper<TRpcMethod> = TRpcMethod extends Callable ? (...rawParams: unknown[]) => RpcSubscriptionsPlan<ReturnType<TRpcMethod>> : never;
type RpcSubscriptionsApiMethod = (...args: any) => any;
export interface RpcSubscriptionsApiMethods {
[methodName: string]: RpcSubscriptionsApiMethod;
}
/**
* Creates a JavaScript proxy that converts _any_ function call called on it to a
* {@link RpcSubscriptionsPlan} by creating an `execute` function that:
*
* - calls the supplied {@link RpcSubscriptionsApiConfig.planExecutor} with a JSON RPC v2 payload
* object with the requested `methodName` and `params` properties, optionally transformed by
* {@link RpcSubscriptionsApiConfig.requestTransformer}.
*
* @example
* ```ts
* // For example, given this `RpcSubscriptionsApi`:
* const rpcSubscriptionsApi = createJsonRpcSubscriptionsApi({
* async planExecutor({ channel, request }) {
* await channel.send(request);
* return {
* ...channel,
* on(type, listener, options) {
* if (type !== 'message') {
* return channel.on(type, listener, options);
* }
* return channel.on(
* 'message',
* function resultGettingListener(message) {
* listener(message.result);
* },
* options,
* );
* }
* }
* },
* requestTransformer: (...rawParams) => rawParams.reverse(),
* });
*
* // ...the following function call:
* rpcSubscriptionsApi.foo('bar', { baz: 'bat' });
*
* // ...will produce a `RpcSubscriptionsPlan` that:
* // - Uses the following payload: { id: 1, jsonrpc: '2.0', method: 'foo', params: [{ baz: 'bat' }, 'bar'] }.
* // - Emits the "result" property of each RPC Subscriptions message.
* ```
*/
export declare function createRpcSubscriptionsApi<TRpcSubscriptionsApiMethods extends RpcSubscriptionsApiMethods>(config: RpcSubscriptionsApiConfig<TRpcSubscriptionsApiMethods>): RpcSubscriptionsApi<TRpcSubscriptionsApiMethods>;
export {};
//# sourceMappingURL=rpc-subscriptions-api.d.ts.map