Skip to content

Commit e58bb22

Browse files
authored
refactor(experimental): add cluster level API for transports
This change introduces cluster-level transports as well as cluster-level JSON RPCs that infer the cluster from the provided transport.
1 parent 61f7ba0 commit e58bb22

File tree

10 files changed

+174
-14
lines changed

10 files changed

+174
-14
lines changed
Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
import { devnet, IRpcApiMethods, mainnet, Rpc, testnet } from '@solana/rpc-types';
2+
3+
import { createJsonRpcApi } from '../apis/methods/methods-api';
4+
import { createJsonRpc } from '../json-rpc';
5+
import { RpcDevnet, RpcMainnet, RpcTestnet } from '../json-rpc-types';
6+
import { createHttpTransport } from '../transports/http/http-transport';
7+
8+
interface MyApiMethods extends IRpcApiMethods {
9+
foo(): number;
10+
bar(): string;
11+
}
12+
13+
const api = createJsonRpcApi<MyApiMethods>();
14+
15+
const genericTransport = createHttpTransport({ url: 'http://localhost:8899' });
16+
const devnetTransport = createHttpTransport({ url: devnet('https://api.devnet.solana.com') });
17+
const testnetTransport = createHttpTransport({ url: testnet('https://api.testnet.solana.com') });
18+
const mainnetTransport = createHttpTransport({ url: mainnet('https://api.mainnet-beta.solana.com') });
19+
20+
// When providing a generic transport, the RPC should be typed as an Rpc
21+
createJsonRpc({ api, transport: genericTransport }) satisfies Rpc<MyApiMethods>;
22+
//@ts-expect-error Should not be a devnet RPC
23+
createJsonRpc({ api, transport: genericTransport }) satisfies RpcDevnet<MyApiMethods>;
24+
//@ts-expect-error Should not be a testnet RPC
25+
createJsonRpc({ api, transport: genericTransport }) satisfies RpcTestnet<MyApiMethods>;
26+
//@ts-expect-error Should not be a mainnet RPC
27+
createJsonRpc({ api, transport: genericTransport }) satisfies RpcMainnet<MyApiMethods>;
28+
29+
// When providing a devnet transport, the RPC should be typed as an RpcDevnet
30+
createJsonRpc({ api, transport: devnetTransport }) satisfies RpcDevnet<MyApiMethods>;
31+
//@ts-expect-error Should not be a testnet RPC
32+
createJsonRpc({ api, transport: devnetTransport }) satisfies RpcTestnet<MyApiMethods>;
33+
//@ts-expect-error Should not be a mainnet RPC
34+
createJsonRpc({ api, transport: devnetTransport }) satisfies RpcMainnet<MyApiMethods>;
35+
36+
// When providing a testnet transport, the RPC should be typed as an RpcTestnet
37+
createJsonRpc({ api, transport: testnetTransport }) satisfies RpcTestnet<MyApiMethods>;
38+
//@ts-expect-error Should not be a devnet RPC
39+
createJsonRpc({ api, transport: testnetTransport }) satisfies RpcDevnet<MyApiMethods>;
40+
//@ts-expect-error Should not be a mainnet RPC
41+
createJsonRpc({ api, transport: testnetTransport }) satisfies RpcMainnet<MyApiMethods>;
42+
43+
// When providing a mainnet transport, the RPC should be typed as an RpcMainnet
44+
createJsonRpc({ api, transport: mainnetTransport }) satisfies RpcMainnet<MyApiMethods>;
45+
//@ts-expect-error Should not be a devnet RPC
46+
createJsonRpc({ api, transport: mainnetTransport }) satisfies RpcDevnet<MyApiMethods>;
47+
//@ts-expect-error Should not be a testnet RPC
48+
createJsonRpc({ api, transport: mainnetTransport }) satisfies RpcTestnet<MyApiMethods>;

packages/rpc-transport/src/__typetests__/methods-api-typetest.ts renamed to packages/rpc-transport/src/apis/__typetests__/methods-api-typetest.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import { IRpcApi, IRpcApiMethods } from '@solana/rpc-types';
22

3-
import { createJsonRpcApi } from '../apis/methods/methods-api';
3+
import { createJsonRpcApi } from '../methods/methods-api';
44

55
type NftCollectionDetailsApiResponse = Readonly<{
66
address: string;

packages/rpc-transport/src/__typetests__/subscriptions-api-typetest.ts renamed to packages/rpc-transport/src/apis/__typetests__/subscriptions-api-typetest.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import { IRpcApiMethods, IRpcSubscriptionsApi } from '@solana/rpc-types';
22

3-
import { createJsonRpcSubscriptionsApi } from '../apis/subscriptions/subscriptions-api';
3+
import { createJsonRpcSubscriptionsApi } from '../subscriptions/subscriptions-api';
44

55
type NftCollectionDetailsApiResponse = Readonly<{
66
address: string;

packages/rpc-transport/src/index.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ export * from './apis/subscriptions/subscriptions-api';
44
export * from './json-rpc';
55
export type { SolanaJsonRpcErrorCode } from './json-rpc-errors';
66
export * from './json-rpc-subscription';
7-
7+
export * from './json-rpc-types';
88
export * from './transports/http/http-transport';
9-
export type { IRpcTransport, IRpcWebSocketTransport } from './transports/transport-types';
9+
export * from './transports/transport-types';
1010
export * from './transports/websocket/websocket-transport';

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

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,10 @@
11
import { IRpcApi, IRpcSubscriptionsApi } from '@solana/rpc-types';
22

3-
import { IRpcTransport, IRpcWebSocketTransport } from './transports/transport-types';
3+
import { IRpcTransport, IRpcTransportWithCluster, IRpcWebSocketTransport } from './transports/transport-types';
44

55
export type RpcConfig<TRpcMethods> = Readonly<{
66
api: IRpcApi<TRpcMethods>;
7-
transport: IRpcTransport;
7+
transport: IRpcTransport | IRpcTransportWithCluster;
88
}>;
99

1010
export type RpcSubscriptionConfig<TRpcMethods> = Readonly<{
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
import { Rpc } from '@solana/rpc-types';
2+
3+
import {
4+
IRpcTransport,
5+
IRpcTransportDevnet,
6+
IRpcTransportMainnet,
7+
IRpcTransportTestnet,
8+
IRpcTransportWithCluster,
9+
} from './transports/transport-types';
10+
11+
export type RpcDevnet<TRpcMethods> = Rpc<TRpcMethods> & { '~cluster': 'devnet' };
12+
export type RpcTestnet<TRpcMethods> = Rpc<TRpcMethods> & { '~cluster': 'testnet' };
13+
export type RpcMainnet<TRpcMethods> = Rpc<TRpcMethods> & { '~cluster': 'mainnet' };
14+
export type RpcFromTransport<
15+
TRpcMethods,
16+
TRpcTransport extends IRpcTransport | IRpcTransportWithCluster,
17+
> = TRpcTransport extends IRpcTransportDevnet
18+
? RpcDevnet<TRpcMethods>
19+
: TRpcTransport extends IRpcTransportTestnet
20+
? RpcTestnet<TRpcMethods>
21+
: TRpcTransport extends IRpcTransportMainnet
22+
? RpcMainnet<TRpcMethods>
23+
: Rpc<TRpcMethods>;

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

Lines changed: 36 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,15 @@
1-
import { PendingRpcRequest, Rpc, RpcRequest, SendOptions } from '@solana/rpc-types';
1+
import { IRpcApi, PendingRpcRequest, Rpc, RpcRequest, SendOptions } from '@solana/rpc-types';
22

33
import { RpcConfig } from './json-rpc-config';
44
import { SolanaJsonRpcError } from './json-rpc-errors';
55
import { createJsonRpcMessage } from './json-rpc-message';
6+
import { RpcDevnet, RpcFromTransport, RpcMainnet, RpcTestnet } from './json-rpc-types';
7+
import {
8+
IRpcTransport,
9+
IRpcTransportDevnet,
10+
IRpcTransportMainnet,
11+
IRpcTransportTestnet,
12+
} from './transports/transport-types';
613

714
interface IHasIdentifier {
815
readonly id: number;
@@ -54,6 +61,32 @@ function makeProxy<TRpcMethods>(rpcConfig: RpcConfig<TRpcMethods>): Rpc<TRpcMeth
5461
}) as Rpc<TRpcMethods>;
5562
}
5663

57-
export function createJsonRpc<TRpcMethods>(rpcConfig: RpcConfig<TRpcMethods>): Rpc<TRpcMethods> {
58-
return makeProxy(rpcConfig);
64+
export function createJsonRpc<TRpcMethods>(
65+
rpcConfig: Readonly<{
66+
api: IRpcApi<TRpcMethods>;
67+
transport: IRpcTransportDevnet;
68+
}>,
69+
): RpcDevnet<TRpcMethods>;
70+
export function createJsonRpc<TRpcMethods>(
71+
rpcConfig: Readonly<{
72+
api: IRpcApi<TRpcMethods>;
73+
transport: IRpcTransportTestnet;
74+
}>,
75+
): RpcTestnet<TRpcMethods>;
76+
export function createJsonRpc<TRpcMethods>(
77+
rpcConfig: Readonly<{
78+
api: IRpcApi<TRpcMethods>;
79+
transport: IRpcTransportMainnet;
80+
}>,
81+
): RpcMainnet<TRpcMethods>;
82+
export function createJsonRpc<TRpcMethods>(
83+
rpcConfig: Readonly<{
84+
api: IRpcApi<TRpcMethods>;
85+
transport: IRpcTransport;
86+
}>,
87+
): Rpc<TRpcMethods>;
88+
export function createJsonRpc<TRpcMethods, TConfig extends RpcConfig<TRpcMethods>>(
89+
rpcConfig: TConfig,
90+
): RpcFromTransport<TRpcMethods, TConfig['transport']> {
91+
return makeProxy(rpcConfig) as RpcFromTransport<TRpcMethods, TConfig['transport']>;
5992
}
Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
import { devnet, mainnet, testnet } from '@solana/rpc-types';
2+
3+
import { IRpcTransport, IRpcTransportDevnet, IRpcTransportMainnet, IRpcTransportTestnet } from '../../transport-types';
4+
import { createHttpTransport } from '../http-transport';
5+
6+
const genericUrl = 'http://localhost:8899';
7+
const devnetUrl = devnet('https://api.devnet.solana.com');
8+
const testnetUrl = testnet('https://api.testnet.solana.com');
9+
const mainnetUrl = mainnet('https://api.mainnet-beta.solana.com');
10+
11+
// When providing a generic URL, the transport should be typed as an IRpcTransport
12+
createHttpTransport({ url: genericUrl }) satisfies IRpcTransport;
13+
//@ts-expect-error Should not be a devnet transport
14+
createHttpTransport({ url: genericUrl }) satisfies IRpcTransportDevnet;
15+
//@ts-expect-error Should not be a testnet transport
16+
createHttpTransport({ url: genericUrl }) satisfies IRpcTransportTestnet;
17+
//@ts-expect-error Should not be a mainnet transport
18+
createHttpTransport({ url: genericUrl }) satisfies IRpcTransportMainnet;
19+
20+
// When providing a devnet URL, the transport should be typed as an IRpcTransportDevnet
21+
createHttpTransport({ url: devnetUrl }) satisfies IRpcTransportDevnet;
22+
//@ts-expect-error Should not be a testnet transport
23+
createHttpTransport({ url: devnetUrl }) satisfies IRpcTransportTestnet;
24+
//@ts-expect-error Should not be a mainnet transport
25+
createHttpTransport({ url: devnetUrl }) satisfies IRpcTransportMainnet;
26+
27+
// When providing a testnet URL, the transport should be typed as an IRpcTransportTestnet
28+
createHttpTransport({ url: testnetUrl }) satisfies IRpcTransportTestnet;
29+
//@ts-expect-error Should not be a devnet transport
30+
createHttpTransport({ url: testnetUrl }) satisfies IRpcTransportDevnet;
31+
//@ts-expect-error Should not be a mainnet transport
32+
createHttpTransport({ url: testnetUrl }) satisfies IRpcTransportMainnet;
33+
34+
// When providing a mainnet URL, the transport should be typed as an IRpcTransportMainnet
35+
createHttpTransport({ url: mainnetUrl }) satisfies IRpcTransportMainnet;
36+
//@ts-expect-error Should not be a devnet transport
37+
createHttpTransport({ url: mainnetUrl }) satisfies IRpcTransportDevnet;
38+
//@ts-expect-error Should not be a testnet transport
39+
createHttpTransport({ url: mainnetUrl }) satisfies IRpcTransportTestnet;

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

Lines changed: 9 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,19 +1,23 @@
1+
import { ClusterUrl } from '@solana/rpc-types';
12
import fetchImpl from 'fetch-impl';
23

3-
import { IRpcTransport } from '../transport-types';
4+
import { IRpcTransport, IRpcTransportFromClusterUrl } from '../transport-types';
45
import { SolanaHttpError } from './http-transport-errors';
56
import {
67
AllowedHttpRequestHeaders,
78
assertIsAllowedHttpRequestHeaders,
89
normalizeHeaders,
910
} from './http-transport-headers';
1011

11-
type Config = Readonly<{
12+
type Config<TClusterUrl extends ClusterUrl> = Readonly<{
1213
headers?: AllowedHttpRequestHeaders;
13-
url: string;
14+
url: TClusterUrl;
1415
}>;
1516

16-
export function createHttpTransport({ headers, url }: Config): IRpcTransport {
17+
export function createHttpTransport<TClusterUrl extends ClusterUrl>({
18+
headers,
19+
url,
20+
}: Config<TClusterUrl>): IRpcTransportFromClusterUrl<TClusterUrl> {
1721
if (__DEV__ && headers) {
1822
assertIsAllowedHttpRequestHeaders(headers);
1923
}
@@ -43,5 +47,5 @@ export function createHttpTransport({ headers, url }: Config): IRpcTransport {
4347
});
4448
}
4549
return (await response.json()) as TResponse;
46-
};
50+
} as IRpcTransportFromClusterUrl<TClusterUrl>;
4751
}

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

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
import { ClusterUrl, DevnetUrl, MainnetUrl, TestnetUrl } from '@solana/rpc-types';
2+
13
import { RpcWebSocketConnection } from './websocket/websocket-connection';
24

35
type RpcTransportConfig = Readonly<{
@@ -8,6 +10,17 @@ type RpcTransportConfig = Readonly<{
810
export interface IRpcTransport {
911
<TResponse>(config: RpcTransportConfig): Promise<TResponse>;
1012
}
13+
export type IRpcTransportDevnet = IRpcTransport & { '~cluster': 'devnet' };
14+
export type IRpcTransportTestnet = IRpcTransport & { '~cluster': 'testnet' };
15+
export type IRpcTransportMainnet = IRpcTransport & { '~cluster': 'mainnet' };
16+
export type IRpcTransportWithCluster = IRpcTransportDevnet | IRpcTransportTestnet | IRpcTransportMainnet;
17+
export type IRpcTransportFromClusterUrl<TClusterUrl extends ClusterUrl> = TClusterUrl extends DevnetUrl
18+
? IRpcTransportDevnet
19+
: TClusterUrl extends TestnetUrl
20+
? IRpcTransportTestnet
21+
: TClusterUrl extends MainnetUrl
22+
? IRpcTransportMainnet
23+
: IRpcTransport;
1124

1225
type RpcWebSocketTransportConfig = Readonly<{
1326
payload: unknown;

0 commit comments

Comments
 (0)