Skip to content

Commit cbf8f38

Browse files
authored
refactor(experimental): add cluster level subscriptions API for transports
This change mirrors the first change in the stack, but for subscriptions.
1 parent 5a6335d commit cbf8f38

File tree

7 files changed

+246
-12
lines changed

7 files changed

+246
-12
lines changed
Lines changed: 93 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,93 @@
1+
import { devnet, IRpcApiSubscriptions, mainnet, RpcSubscriptions, testnet } from '@solana/rpc-types';
2+
3+
import { createJsonRpcSubscriptionsApi } from '../apis/subscriptions/subscriptions-api';
4+
import { createJsonSubscriptionRpc } from '../json-rpc-subscription';
5+
import { RpcSubscriptionsDevnet, RpcSubscriptionsMainnet, RpcSubscriptionsTestnet } from '../json-rpc-types';
6+
import { createWebSocketTransport } from '../transports/websocket/websocket-transport';
7+
8+
interface MySubscriptionApiMethods extends IRpcApiSubscriptions {
9+
foo(): number;
10+
bar(): string;
11+
}
12+
13+
const api = null as unknown as ReturnType<typeof createJsonRpcSubscriptionsApi<MySubscriptionApiMethods>>;
14+
15+
const genericTransport = createWebSocketTransport({ sendBufferHighWatermark: 0, url: 'http://localhost:8899' });
16+
const devnetTransport = createWebSocketTransport({
17+
sendBufferHighWatermark: 0,
18+
url: devnet('https://api.devnet.solana.com'),
19+
});
20+
const testnetTransport = createWebSocketTransport({
21+
sendBufferHighWatermark: 0,
22+
url: testnet('https://api.testnet.solana.com'),
23+
});
24+
const mainnetTransport = createWebSocketTransport({
25+
sendBufferHighWatermark: 0,
26+
url: mainnet('https://api.mainnet-beta.solana.com'),
27+
});
28+
29+
// When providing a generic transport, the RPC should be typed as RpcSubscription
30+
createJsonSubscriptionRpc({ api, transport: genericTransport }) satisfies RpcSubscriptions<MySubscriptionApiMethods>;
31+
createJsonSubscriptionRpc({
32+
api,
33+
transport: genericTransport,
34+
//@ts-expect-error Should not be a devnet transport
35+
}) satisfies RpcSubscriptionsDevnet<MySubscriptionApiMethods>;
36+
createJsonSubscriptionRpc({
37+
api,
38+
transport: genericTransport,
39+
//@ts-expect-error Should not be a testnet transport
40+
}) satisfies RpcSubscriptionsTestnet<MySubscriptionApiMethods>;
41+
createJsonSubscriptionRpc({
42+
api,
43+
transport: genericTransport,
44+
//@ts-expect-error Should not be a mainnet transport
45+
}) satisfies RpcSubscriptionsMainnet<MySubscriptionApiMethods>;
46+
47+
// When providing a devnet transport, the RPC should be typed as RpcSubscriptionsDevnet
48+
createJsonSubscriptionRpc({
49+
api,
50+
transport: devnetTransport,
51+
}) satisfies RpcSubscriptionsDevnet<MySubscriptionApiMethods>;
52+
createJsonSubscriptionRpc({
53+
api,
54+
transport: devnetTransport,
55+
//@ts-expect-error Should not be a testnet transport
56+
}) satisfies RpcSubscriptionsTestnet<MySubscriptionApiMethods>;
57+
createJsonSubscriptionRpc({
58+
api,
59+
transport: devnetTransport,
60+
//@ts-expect-error Should not be a mainnet transport
61+
}) satisfies RpcSubscriptionsMainnet<MySubscriptionApiMethods>;
62+
63+
// When providing a testnet transport, the RPC should be typed as RpcSubscriptionsTestnet
64+
createJsonSubscriptionRpc({
65+
api,
66+
transport: testnetTransport,
67+
}) satisfies RpcSubscriptionsTestnet<MySubscriptionApiMethods>;
68+
createJsonSubscriptionRpc({
69+
api,
70+
transport: testnetTransport,
71+
//@ts-expect-error Should not be a devnet transport
72+
}) satisfies RpcSubscriptionsDevnet<MySubscriptionApiMethods>;
73+
createJsonSubscriptionRpc({
74+
api,
75+
transport: testnetTransport,
76+
//@ts-expect-error Should not be a mainnet transport
77+
}) satisfies RpcSubscriptionsMainnet<MySubscriptionApiMethods>;
78+
79+
// When providing a mainnet transport, the RPC should be typed as RpcSubscriptionsMainnet
80+
createJsonSubscriptionRpc({
81+
api,
82+
transport: mainnetTransport,
83+
}) satisfies RpcSubscriptionsMainnet<MySubscriptionApiMethods>;
84+
createJsonSubscriptionRpc({
85+
api,
86+
transport: mainnetTransport,
87+
//@ts-expect-error Should not be a devnet transport
88+
}) satisfies RpcSubscriptionsDevnet<MySubscriptionApiMethods>;
89+
createJsonSubscriptionRpc({
90+
api,
91+
transport: mainnetTransport,
92+
//@ts-expect-error Should not be a testnet transport
93+
}) satisfies RpcSubscriptionsTestnet<MySubscriptionApiMethods>;
Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,11 @@
11
import { IRpcApi, IRpcSubscriptionsApi } from '@solana/rpc-types';
22

3-
import { IRpcTransport, IRpcTransportWithCluster, IRpcWebSocketTransport } from './transports/transport-types';
3+
import {
4+
IRpcTransport,
5+
IRpcTransportWithCluster,
6+
IRpcWebSocketTransport,
7+
IRpcWebSocketTransportWithCluster,
8+
} from './transports/transport-types';
49

510
export type RpcConfig<TRpcMethods> = Readonly<{
611
api: IRpcApi<TRpcMethods>;
@@ -9,5 +14,5 @@ export type RpcConfig<TRpcMethods> = Readonly<{
914

1015
export type RpcSubscriptionConfig<TRpcMethods> = Readonly<{
1116
api: IRpcSubscriptionsApi<TRpcMethods>;
12-
transport: IRpcWebSocketTransport;
17+
transport: IRpcWebSocketTransport | IRpcWebSocketTransportWithCluster;
1318
}>;

packages/rpc-transport/src/json-rpc-subscription.ts

Lines changed: 47 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,27 @@
1-
import { PendingRpcSubscription, RpcSubscription, RpcSubscriptions, SubscribeOptions } from '@solana/rpc-types';
1+
import {
2+
IRpcSubscriptionsApi,
3+
PendingRpcSubscription,
4+
RpcSubscription,
5+
RpcSubscriptions,
6+
SubscribeOptions,
7+
} from '@solana/rpc-types';
28

39
import { JsonRpcResponse } from './json-rpc';
410
import { RpcSubscriptionConfig } from './json-rpc-config';
511
import { SolanaJsonRpcError } from './json-rpc-errors';
612
import { createJsonRpcMessage } from './json-rpc-message';
13+
import {
14+
RpcSubscriptionsDevnet,
15+
RpcSubscriptionsFromTransport,
16+
RpcSubscriptionsMainnet,
17+
RpcSubscriptionsTestnet,
18+
} from './json-rpc-types';
19+
import {
20+
IRpcWebSocketTransport,
21+
IRpcWebSocketTransportDevnet,
22+
IRpcWebSocketTransportMainnet,
23+
IRpcWebSocketTransportTestnet,
24+
} from './transports/transport-types';
725

826
type JsonRpcNotification<TNotification> = Readonly<{
927
params: Readonly<{
@@ -136,7 +154,32 @@ function makeProxy<TRpcSubscriptionMethods>(
136154
}
137155

138156
export function createJsonSubscriptionRpc<TRpcSubscriptionMethods>(
139-
rpcConfig: RpcSubscriptionConfig<TRpcSubscriptionMethods>,
140-
): RpcSubscriptions<TRpcSubscriptionMethods> {
141-
return makeProxy(rpcConfig);
157+
rpcConfig: Readonly<{
158+
api: IRpcSubscriptionsApi<TRpcSubscriptionMethods>;
159+
transport: IRpcWebSocketTransportDevnet;
160+
}>,
161+
): RpcSubscriptionsDevnet<TRpcSubscriptionMethods>;
162+
export function createJsonSubscriptionRpc<TRpcSubscriptionMethods>(
163+
rpcConfig: Readonly<{
164+
api: IRpcSubscriptionsApi<TRpcSubscriptionMethods>;
165+
transport: IRpcWebSocketTransportTestnet;
166+
}>,
167+
): RpcSubscriptionsTestnet<TRpcSubscriptionMethods>;
168+
export function createJsonSubscriptionRpc<TRpcSubscriptionMethods>(
169+
rpcConfig: Readonly<{
170+
api: IRpcSubscriptionsApi<TRpcSubscriptionMethods>;
171+
transport: IRpcWebSocketTransportMainnet;
172+
}>,
173+
): RpcSubscriptionsMainnet<TRpcSubscriptionMethods>;
174+
export function createJsonSubscriptionRpc<TRpcSubscriptionMethods>(
175+
rpcConfig: Readonly<{
176+
api: IRpcSubscriptionsApi<TRpcSubscriptionMethods>;
177+
transport: IRpcWebSocketTransport;
178+
}>,
179+
): RpcSubscriptions<TRpcSubscriptionMethods>;
180+
export function createJsonSubscriptionRpc<
181+
TRpcSubscriptionMethods,
182+
TConfig extends RpcSubscriptionConfig<TRpcSubscriptionMethods>,
183+
>(rpcConfig: TConfig): RpcSubscriptionsFromTransport<TRpcSubscriptionMethods, TConfig['transport']> {
184+
return makeProxy(rpcConfig) as RpcSubscriptionsFromTransport<TRpcSubscriptionMethods, TConfig['transport']>;
142185
}

packages/rpc-transport/src/json-rpc-types.ts

Lines changed: 26 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,16 @@
1-
import { Rpc } from '@solana/rpc-types';
1+
import { Rpc, RpcSubscriptions } from '@solana/rpc-types';
22

33
import {
44
IRpcTransport,
55
IRpcTransportDevnet,
66
IRpcTransportMainnet,
77
IRpcTransportTestnet,
88
IRpcTransportWithCluster,
9+
IRpcWebSocketTransport,
10+
IRpcWebSocketTransportDevnet,
11+
IRpcWebSocketTransportMainnet,
12+
IRpcWebSocketTransportTestnet,
13+
IRpcWebSocketTransportWithCluster,
914
} from './transports/transport-types';
1015

1116
export type RpcDevnet<TRpcMethods> = Rpc<TRpcMethods> & { '~cluster': 'devnet' };
@@ -21,3 +26,23 @@ export type RpcFromTransport<
2126
: TRpcTransport extends IRpcTransportMainnet
2227
? RpcMainnet<TRpcMethods>
2328
: Rpc<TRpcMethods>;
29+
30+
export type RpcSubscriptionsDevnet<TRpcSubscriptionMethods> = RpcSubscriptions<TRpcSubscriptionMethods> & {
31+
'~cluster': 'devnet';
32+
};
33+
export type RpcSubscriptionsTestnet<TRpcSubscriptionMethods> = RpcSubscriptions<TRpcSubscriptionMethods> & {
34+
'~cluster': 'testnet';
35+
};
36+
export type RpcSubscriptionsMainnet<TRpcSubscriptionMethods> = RpcSubscriptions<TRpcSubscriptionMethods> & {
37+
'~cluster': 'mainnet';
38+
};
39+
export type RpcSubscriptionsFromTransport<
40+
TRpcSubscriptionMethods,
41+
TRpcTransport extends IRpcWebSocketTransport | IRpcWebSocketTransportWithCluster,
42+
> = TRpcTransport extends IRpcWebSocketTransportDevnet
43+
? RpcSubscriptionsDevnet<TRpcSubscriptionMethods>
44+
: TRpcTransport extends IRpcWebSocketTransportTestnet
45+
? RpcSubscriptionsTestnet<TRpcSubscriptionMethods>
46+
: TRpcTransport extends IRpcWebSocketTransportMainnet
47+
? RpcSubscriptionsMainnet<TRpcSubscriptionMethods>
48+
: RpcSubscriptions<TRpcSubscriptionMethods>;

packages/rpc-transport/src/transports/transport-types.ts

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,8 @@ import { ClusterUrl, DevnetUrl, MainnetUrl, TestnetUrl } from '@solana/rpc-types
22

33
import { RpcWebSocketConnection } from './websocket/websocket-connection';
44

5+
// HTTP transport
6+
57
type RpcTransportConfig = Readonly<{
68
payload: unknown;
79
signal?: AbortSignal;
@@ -27,6 +29,8 @@ type RpcWebSocketTransportConfig = Readonly<{
2729
signal: AbortSignal;
2830
}>;
2931

32+
// WebSocket transport
33+
3034
export interface IRpcWebSocketTransport {
3135
(config: RpcWebSocketTransportConfig): Promise<
3236
Readonly<
@@ -36,3 +40,18 @@ export interface IRpcWebSocketTransport {
3640
>
3741
>;
3842
}
43+
44+
export type IRpcWebSocketTransportDevnet = IRpcWebSocketTransport & { '~cluster': 'devnet' };
45+
export type IRpcWebSocketTransportTestnet = IRpcWebSocketTransport & { '~cluster': 'testnet' };
46+
export type IRpcWebSocketTransportMainnet = IRpcWebSocketTransport & { '~cluster': 'mainnet' };
47+
export type IRpcWebSocketTransportWithCluster =
48+
| IRpcWebSocketTransportDevnet
49+
| IRpcWebSocketTransportTestnet
50+
| IRpcWebSocketTransportMainnet;
51+
export type IRpcWebSocketTransportFromClusterUrl<TClusterUrl extends ClusterUrl> = TClusterUrl extends DevnetUrl
52+
? IRpcWebSocketTransportDevnet
53+
: TClusterUrl extends TestnetUrl
54+
? IRpcWebSocketTransportTestnet
55+
: TClusterUrl extends MainnetUrl
56+
? IRpcWebSocketTransportMainnet
57+
: IRpcWebSocketTransport;
Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
import { devnet, mainnet, testnet } from '@solana/rpc-types';
2+
3+
import {
4+
IRpcWebSocketTransport,
5+
IRpcWebSocketTransportDevnet,
6+
IRpcWebSocketTransportMainnet,
7+
IRpcWebSocketTransportTestnet,
8+
} from '../../transport-types';
9+
import { createWebSocketTransport } from '../websocket-transport';
10+
11+
const genericConfig = { sendBufferHighWatermark: 0, url: 'http://localhost:8899' };
12+
const devnetConfig = { sendBufferHighWatermark: 0, url: devnet('https://api.devnet.solana.com') };
13+
const testnetConfig = { sendBufferHighWatermark: 0, url: testnet('https://api.testnet.solana.com') };
14+
const mainnetConfig = { sendBufferHighWatermark: 0, url: mainnet('https://api.mainnet-beta.solana.com') };
15+
16+
// When providing a generic URL, the transport should be typed as an IRpcWebSocketTransport
17+
createWebSocketTransport(genericConfig) satisfies IRpcWebSocketTransport;
18+
//@ts-expect-error Should not be a devnet transport
19+
createWebSocketTransport(genericConfig) satisfies IRpcWebSocketTransportDevnet;
20+
//@ts-expect-error Should not be a testnet transport
21+
createWebSocketTransport(genericConfig) satisfies IRpcWebSocketTransportTestnet;
22+
//@ts-expect-error Should not be a mainnet transport
23+
createWebSocketTransport(genericConfig) satisfies IRpcWebSocketTransportMainnet;
24+
25+
// When providing a devnet URL, the transport should be typed as an IRpcWebSocketTransportDevnet
26+
createWebSocketTransport(devnetConfig) satisfies IRpcWebSocketTransportDevnet;
27+
//@ts-expect-error Should not be a testnet transport
28+
createWebSocketTransport(devnetConfig) satisfies IRpcWebSocketTransportTestnet;
29+
//@ts-expect-error Should not be a mainnet transport
30+
createWebSocketTransport(devnetConfig) satisfies IRpcWebSocketTransportMainnet;
31+
32+
// When providing a testnet URL, the transport should be typed as an IRpcWebSocketTransportTestnet
33+
createWebSocketTransport(testnetConfig) satisfies IRpcWebSocketTransportTestnet;
34+
//@ts-expect-error Should not be a devnet transport
35+
createWebSocketTransport(testnetConfig) satisfies IRpcWebSocketTransportDevnet;
36+
//@ts-expect-error Should not be a mainnet transport
37+
createWebSocketTransport(testnetConfig) satisfies IRpcWebSocketTransportMainnet;
38+
39+
// When providing a mainnet URL, the transport should be typed as an IRpcWebSocketTransportMainnet
40+
createWebSocketTransport(mainnetConfig) satisfies IRpcWebSocketTransportMainnet;
41+
//@ts-expect-error Should not be a devnet transport
42+
createWebSocketTransport(mainnetConfig) satisfies IRpcWebSocketTransportDevnet;
43+
//@ts-expect-error Should not be a testnet transport
44+
createWebSocketTransport(mainnetConfig) satisfies IRpcWebSocketTransportTestnet;

packages/rpc-transport/src/transports/websocket/websocket-transport.ts

Lines changed: 10 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,17 @@
1-
import { IRpcWebSocketTransport } from '../transport-types';
1+
import { ClusterUrl } from '@solana/rpc-types';
2+
3+
import { IRpcWebSocketTransport, IRpcWebSocketTransportFromClusterUrl } from '../transport-types';
24
import { createWebSocketConnection } from './websocket-connection';
35

4-
type Config = Readonly<{
6+
type Config<TClusterUrl extends ClusterUrl> = Readonly<{
57
sendBufferHighWatermark: number;
6-
url: string;
8+
url: TClusterUrl;
79
}>;
810

9-
export function createWebSocketTransport({ sendBufferHighWatermark, url }: Config): IRpcWebSocketTransport {
11+
export function createWebSocketTransport<TClusterUrl extends ClusterUrl>({
12+
sendBufferHighWatermark,
13+
url,
14+
}: Config<TClusterUrl>): IRpcWebSocketTransportFromClusterUrl<TClusterUrl> {
1015
if (/^wss?:/i.test(url) === false) {
1116
const protocolMatch = url.match(/^([^:]+):/);
1217
throw new DOMException(
@@ -28,5 +33,5 @@ export function createWebSocketTransport({ sendBufferHighWatermark, url }: Confi
2833
[Symbol.asyncIterator]: connection[Symbol.asyncIterator].bind(connection),
2934
send_DO_NOT_USE_OR_YOU_WILL_BE_FIRED: connection.send.bind(connection),
3035
};
31-
};
36+
} as IRpcWebSocketTransportFromClusterUrl<TClusterUrl>;
3237
}

0 commit comments

Comments
 (0)