Skip to content

Commit

Permalink
refactor(experimental): add cluster level subscriptions API for trans…
Browse files Browse the repository at this point in the history
…ports
  • Loading branch information
buffalojoec committed Feb 1, 2024
1 parent a1db0ef commit 3b57349
Show file tree
Hide file tree
Showing 6 changed files with 233 additions and 9 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,101 @@
import {
devnet,
IRpcApiSubscriptions,
mainnet,
RpcSubscriptions,
RpcSubscriptionsDevnet,
RpcSubscriptionsMainnet,
RpcSubscriptionsTestnet,
testnet,
} from '@solana/rpc-types';

import { createJsonRpcSubscriptionsApi } from '../apis/subscriptions/subscriptions-api';
import { createJsonSubscriptionRpc } from '../json-rpc-subscription';
import { createWebSocketTransport } from '../transports/websocket/websocket-transport';

interface MySubscriptionApiMethods extends IRpcApiSubscriptions {
foo(): number;
bar(): string;
}

const api = null as unknown as ReturnType<typeof createJsonRpcSubscriptionsApi<MySubscriptionApiMethods>>;

const genericTransport = createWebSocketTransport({ sendBufferHighWatermark: 0, url: 'http://localhost:8899' });
const devnetTransport = createWebSocketTransport({
sendBufferHighWatermark: 0,
url: devnet('https://api.devnet.solana.com'),
});
const testnetTransport = createWebSocketTransport({
sendBufferHighWatermark: 0,
url: testnet('https://api.testnet.solana.com'),
});
const mainnetTransport = createWebSocketTransport({
sendBufferHighWatermark: 0,
url: mainnet('https://api.mainnet-beta.solana.com'),
});

// When providing a generic transport, the RPC should be typed as RpcSubscription
createJsonSubscriptionRpc({ api, transport: genericTransport }) satisfies RpcSubscriptions<MySubscriptionApiMethods>;
createJsonSubscriptionRpc({
api,
transport: genericTransport,
//@ts-expect-error Should not be a devnet transport
}) satisfies RpcSubscriptionsDevnet<MySubscriptionApiMethods>;
createJsonSubscriptionRpc({
api,
transport: genericTransport,
//@ts-expect-error Should not be a testnet transport
}) satisfies RpcSubscriptionsTestnet<MySubscriptionApiMethods>;
createJsonSubscriptionRpc({
api,
transport: genericTransport,
//@ts-expect-error Should not be a mainnet transport
}) satisfies RpcSubscriptionsMainnet<MySubscriptionApiMethods>;

// When providing a devnet transport, the RPC should be typed as RpcSubscriptionsDevnet
createJsonSubscriptionRpc({
api,
transport: devnetTransport,
}) satisfies RpcSubscriptionsDevnet<MySubscriptionApiMethods>;
createJsonSubscriptionRpc({
api,
transport: devnetTransport,
//@ts-expect-error Should not be a testnet transport
}) satisfies RpcSubscriptionsTestnet<MySubscriptionApiMethods>;
createJsonSubscriptionRpc({
api,
transport: devnetTransport,
//@ts-expect-error Should not be a mainnet transport
}) satisfies RpcSubscriptionsMainnet<MySubscriptionApiMethods>;

// When providing a testnet transport, the RPC should be typed as RpcSubscriptionsTestnet
createJsonSubscriptionRpc({
api,
transport: testnetTransport,
}) satisfies RpcSubscriptionsTestnet<MySubscriptionApiMethods>;
createJsonSubscriptionRpc({
api,
transport: testnetTransport,
//@ts-expect-error Should not be a devnet transport
}) satisfies RpcSubscriptionsDevnet<MySubscriptionApiMethods>;
createJsonSubscriptionRpc({
api,
transport: testnetTransport,
//@ts-expect-error Should not be a mainnet transport
}) satisfies RpcSubscriptionsMainnet<MySubscriptionApiMethods>;

// When providing a mainnet transport, the RPC should be typed as RpcSubscriptionsMainnet
createJsonSubscriptionRpc({
api,
transport: mainnetTransport,
}) satisfies RpcSubscriptionsMainnet<MySubscriptionApiMethods>;
createJsonSubscriptionRpc({
api,
transport: mainnetTransport,
//@ts-expect-error Should not be a devnet transport
}) satisfies RpcSubscriptionsDevnet<MySubscriptionApiMethods>;
createJsonSubscriptionRpc({
api,
transport: mainnetTransport,
//@ts-expect-error Should not be a testnet transport
}) satisfies RpcSubscriptionsTestnet<MySubscriptionApiMethods>;
4 changes: 2 additions & 2 deletions packages/rpc-transport/src/json-rpc-config.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { IIRpcTransport, IRpcApi, IRpcSubscriptionsApi, IRpcWebSocketTransport } from '@solana/rpc-types';
import { IIRpcTransport, IIRpcWebSocketTransport, IRpcApi, IRpcSubscriptionsApi } from '@solana/rpc-types';

export type RpcConfig<TRpcMethods> = Readonly<{
api: IRpcApi<TRpcMethods>;
Expand All @@ -7,5 +7,5 @@ export type RpcConfig<TRpcMethods> = Readonly<{

export type RpcSubscriptionConfig<TRpcMethods> = Readonly<{
api: IRpcSubscriptionsApi<TRpcMethods>;
transport: IRpcWebSocketTransport;
transport: IIRpcWebSocketTransport;
}>;
47 changes: 43 additions & 4 deletions packages/rpc-transport/src/json-rpc-subscription.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,18 @@
import { PendingRpcSubscription, RpcSubscription, RpcSubscriptions, SubscribeOptions } from '@solana/rpc-types';
import {
IRpcSubscriptionsApi,
IRpcWebSocketTransport,
IRpcWebSocketTransportDevnet,
IRpcWebSocketTransportMainnet,
IRpcWebSocketTransportTestnet,
PendingRpcSubscription,
RpcSubscription,
RpcSubscriptions,
RpcSubscriptionsDevnet,
RpcSubscriptionsFromTransport,
RpcSubscriptionsMainnet,
RpcSubscriptionsTestnet,
SubscribeOptions,
} from '@solana/rpc-types';

import { JsonRpcResponse } from './json-rpc';
import { RpcSubscriptionConfig } from './json-rpc-config';
Expand Down Expand Up @@ -136,7 +150,32 @@ function makeProxy<TRpcSubscriptionMethods>(
}

export function createJsonSubscriptionRpc<TRpcSubscriptionMethods>(
rpcConfig: RpcSubscriptionConfig<TRpcSubscriptionMethods>,
): RpcSubscriptions<TRpcSubscriptionMethods> {
return makeProxy(rpcConfig);
rpcConfig: Readonly<{
api: IRpcSubscriptionsApi<TRpcSubscriptionMethods>;
transport: IRpcWebSocketTransportDevnet;
}>,
): RpcSubscriptionsDevnet<TRpcSubscriptionMethods>;
export function createJsonSubscriptionRpc<TRpcSubscriptionMethods>(
rpcConfig: Readonly<{
api: IRpcSubscriptionsApi<TRpcSubscriptionMethods>;
transport: IRpcWebSocketTransportTestnet;
}>,
): RpcSubscriptionsTestnet<TRpcSubscriptionMethods>;
export function createJsonSubscriptionRpc<TRpcSubscriptionMethods>(
rpcConfig: Readonly<{
api: IRpcSubscriptionsApi<TRpcSubscriptionMethods>;
transport: IRpcWebSocketTransportMainnet;
}>,
): RpcSubscriptionsMainnet<TRpcSubscriptionMethods>;
export function createJsonSubscriptionRpc<TRpcSubscriptionMethods>(
rpcConfig: Readonly<{
api: IRpcSubscriptionsApi<TRpcSubscriptionMethods>;
transport: IRpcWebSocketTransport;
}>,
): RpcSubscriptions<TRpcSubscriptionMethods>;
export function createJsonSubscriptionRpc<
TRpcSubscriptionMethods,
TConfig extends RpcSubscriptionConfig<TRpcSubscriptionMethods>,
>(rpcConfig: TConfig): RpcSubscriptionsFromTransport<TRpcSubscriptionMethods, TConfig['transport']> {
return makeProxy(rpcConfig) as RpcSubscriptionsFromTransport<TRpcSubscriptionMethods, TConfig['transport']>;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
import {
devnet,
IRpcWebSocketTransport,
IRpcWebSocketTransportDevnet,
IRpcWebSocketTransportMainnet,
IRpcWebSocketTransportTestnet,
mainnet,
testnet,
} from '@solana/rpc-types';

import { createWebSocketTransport } from '../websocket-transport';

const genericConfig = { sendBufferHighWatermark: 0, url: 'http://localhost:8899' };
const devnetConfig = { sendBufferHighWatermark: 0, url: devnet('https://api.devnet.solana.com') };
const testnetConfig = { sendBufferHighWatermark: 0, url: testnet('https://api.testnet.solana.com') };
const mainnetConfig = { sendBufferHighWatermark: 0, url: mainnet('https://api.mainnet-beta.solana.com') };

// When providing a generic URL, the transport should be typed as an IRpcWebSocketTransport
createWebSocketTransport(genericConfig) satisfies IRpcWebSocketTransport;
//@ts-expect-error Should not be a devnet transport
createWebSocketTransport(genericConfig) satisfies IRpcWebSocketTransportDevnet;
//@ts-expect-error Should not be a testnet transport
createWebSocketTransport(genericConfig) satisfies IRpcWebSocketTransportTestnet;
//@ts-expect-error Should not be a mainnet transport
createWebSocketTransport(genericConfig) satisfies IRpcWebSocketTransportMainnet;

// When providing a devnet URL, the transport should be typed as an IRpcWebSocketTransportDevnet
createWebSocketTransport(devnetConfig) satisfies IRpcWebSocketTransportDevnet;
//@ts-expect-error Should not be a testnet transport
createWebSocketTransport(devnetConfig) satisfies IRpcWebSocketTransportTestnet;
//@ts-expect-error Should not be a mainnet transport
createWebSocketTransport(devnetConfig) satisfies IRpcWebSocketTransportMainnet;

// When providing a testnet URL, the transport should be typed as an IRpcWebSocketTransportTestnet
createWebSocketTransport(testnetConfig) satisfies IRpcWebSocketTransportTestnet;
//@ts-expect-error Should not be a devnet transport
createWebSocketTransport(testnetConfig) satisfies IRpcWebSocketTransportDevnet;
//@ts-expect-error Should not be a mainnet transport
createWebSocketTransport(testnetConfig) satisfies IRpcWebSocketTransportMainnet;

// When providing a mainnet URL, the transport should be typed as an IRpcWebSocketTransportMainnet
createWebSocketTransport(mainnetConfig) satisfies IRpcWebSocketTransportMainnet;
//@ts-expect-error Should not be a devnet transport
createWebSocketTransport(mainnetConfig) satisfies IRpcWebSocketTransportDevnet;
//@ts-expect-error Should not be a testnet transport
createWebSocketTransport(mainnetConfig) satisfies IRpcWebSocketTransportTestnet;
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { IRpcWebSocketTransport } from '@solana/rpc-types';
import { IRpcWebSocketTransport, IRpcWebSocketTransportFromClusterUrl } from '@solana/rpc-types';

import { createWebSocketConnection } from './websocket-connection';

Expand All @@ -7,7 +7,10 @@ type Config = Readonly<{
url: string;
}>;

export function createWebSocketTransport({ sendBufferHighWatermark, url }: Config): IRpcWebSocketTransport {
export function createWebSocketTransport<TConfig extends Config>({
sendBufferHighWatermark,
url,
}: TConfig): IRpcWebSocketTransportFromClusterUrl<TConfig['url']> {
if (/^wss?:/i.test(url) === false) {
const protocolMatch = url.match(/^([^:]+):/);
throw new DOMException(
Expand All @@ -29,5 +32,5 @@ export function createWebSocketTransport({ sendBufferHighWatermark, url }: Confi
[Symbol.asyncIterator]: connection[Symbol.asyncIterator].bind(connection),
send_DO_NOT_USE_OR_YOU_WILL_BE_FIRED: connection.send.bind(connection),
};
};
} as IRpcWebSocketTransportFromClusterUrl<TConfig['url']>;
}
35 changes: 35 additions & 0 deletions packages/rpc-types/src/rpc-api.ts
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,22 @@ export interface IRpcWebSocketTransport {
>
>;
}
export type IRpcWebSocketTransportDevnet = IRpcWebSocketTransport & { '~cluster': 'devnet' };
export type IRpcWebSocketTransportTestnet = IRpcWebSocketTransport & { '~cluster': 'testnet' };
export type IRpcWebSocketTransportMainnet = IRpcWebSocketTransport & { '~cluster': 'mainnet' };
export type IIRpcWebSocketTransport =
| IRpcWebSocketTransport
| IRpcWebSocketTransportDevnet
| IRpcWebSocketTransportTestnet
| IRpcWebSocketTransportMainnet;
export type IRpcWebSocketTransportFromClusterUrl<TClusterUrl extends ClusterUrl> = TClusterUrl extends DevnetUrl
? IRpcWebSocketTransportDevnet
: TClusterUrl extends TestnetUrl
? IRpcWebSocketTransportTestnet
: TClusterUrl extends MainnetUrl
? IRpcWebSocketTransportMainnet
: IRpcWebSocketTransport;

/**
* Public RPC API.
*/
Expand All @@ -70,6 +86,25 @@ export type RpcFromTransport<
: Rpc<TRpcMethods>;

export type RpcSubscriptions<TRpcSubscriptionMethods> = RpcSubscriptionMethods<TRpcSubscriptionMethods>;
export type RpcSubscriptionsDevnet<TRpcSubscriptionMethods> = RpcSubscriptionMethods<TRpcSubscriptionMethods> & {
'~cluster': 'devnet';
};
export type RpcSubscriptionsTestnet<TRpcSubscriptionMethods> = RpcSubscriptionMethods<TRpcSubscriptionMethods> & {
'~cluster': 'testnet';
};
export type RpcSubscriptionsMainnet<TRpcSubscriptionMethods> = RpcSubscriptionMethods<TRpcSubscriptionMethods> & {
'~cluster': 'mainnet';
};
export type RpcSubscriptionsFromTransport<
TRpcSubscriptionMethods,
TRpcTransport extends IIRpcWebSocketTransport,
> = TRpcTransport extends IRpcWebSocketTransportDevnet
? RpcSubscriptionsDevnet<TRpcSubscriptionMethods>
: TRpcTransport extends IRpcWebSocketTransportTestnet
? RpcSubscriptionsTestnet<TRpcSubscriptionMethods>
: TRpcTransport extends IRpcWebSocketTransportMainnet
? RpcSubscriptionsMainnet<TRpcSubscriptionMethods>
: RpcSubscriptions<TRpcSubscriptionMethods>;

/**
* Public RPC API methods.
Expand Down

0 comments on commit 3b57349

Please sign in to comment.