Skip to content

Commit

Permalink
refactor(experimental): add cluster level API for transports
Browse files Browse the repository at this point in the history
  • Loading branch information
buffalojoec committed Feb 1, 2024
1 parent 2f693f8 commit 43492b5
Show file tree
Hide file tree
Showing 9 changed files with 174 additions and 12 deletions.
47 changes: 47 additions & 0 deletions packages/rpc-transport/src/__typetests__/json-rpc-typetest.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
import { devnet, IRpcApiMethods, mainnet, Rpc, RpcDevnet, RpcMainnet, RpcTestnet, testnet } from '@solana/rpc-types';

import { createJsonRpcApi } from '../apis/methods/methods-api';
import { createJsonRpc } from '../json-rpc';
import { createHttpTransport } from '../transports/http/http-transport';

interface MyApiMethods extends IRpcApiMethods {
foo(): number;
bar(): string;
}

const api = createJsonRpcApi<MyApiMethods>();

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

// When providing a generic transport, the RPC should be typed as an Rpc
createJsonRpc({ api, transport: genericTransport }) satisfies Rpc<MyApiMethods>;
//@ts-expect-error Should not be a devnet RPC
createJsonRpc({ api, transport: genericTransport }) satisfies RpcDevnet<MyApiMethods>;
//@ts-expect-error Should not be a testnet RPC
createJsonRpc({ api, transport: genericTransport }) satisfies RpcTestnet<MyApiMethods>;
//@ts-expect-error Should not be a mainnet RPC
createJsonRpc({ api, transport: genericTransport }) satisfies RpcMainnet<MyApiMethods>;

// When providing a devnet transport, the RPC should be typed as an RpcDevnet
createJsonRpc({ api, transport: devnetTransport }) satisfies RpcDevnet<MyApiMethods>;
//@ts-expect-error Should not be a testnet RPC
createJsonRpc({ api, transport: devnetTransport }) satisfies RpcTestnet<MyApiMethods>;
//@ts-expect-error Should not be a mainnet RPC
createJsonRpc({ api, transport: devnetTransport }) satisfies RpcMainnet<MyApiMethods>;

// When providing a testnet transport, the RPC should be typed as an RpcTestnet
createJsonRpc({ api, transport: testnetTransport }) satisfies RpcTestnet<MyApiMethods>;
//@ts-expect-error Should not be a devnet RPC
createJsonRpc({ api, transport: testnetTransport }) satisfies RpcDevnet<MyApiMethods>;
//@ts-expect-error Should not be a mainnet RPC
createJsonRpc({ api, transport: testnetTransport }) satisfies RpcMainnet<MyApiMethods>;

// When providing a mainnet transport, the RPC should be typed as an RpcMainnet
createJsonRpc({ api, transport: mainnetTransport }) satisfies RpcMainnet<MyApiMethods>;
//@ts-expect-error Should not be a devnet RPC
createJsonRpc({ api, transport: mainnetTransport }) satisfies RpcDevnet<MyApiMethods>;
//@ts-expect-error Should not be a testnet RPC
createJsonRpc({ api, transport: mainnetTransport }) satisfies RpcTestnet<MyApiMethods>;
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { IRpcApi, IRpcApiMethods } from '@solana/rpc-types';

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

type NftCollectionDetailsApiResponse = Readonly<{
address: string;
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { IRpcApiMethods, IRpcSubscriptionsApi } from '@solana/rpc-types';

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

type NftCollectionDetailsApiResponse = Readonly<{
address: string;
Expand Down
1 change: 0 additions & 1 deletion packages/rpc-transport/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@ export * from './apis/subscriptions/subscriptions-api';
export * from './json-rpc';
export type { SolanaJsonRpcErrorCode } from './json-rpc-errors';
export * from './json-rpc-subscription';

export * from './transports/http/http-transport';
export type { IRpcWebSocketTransport } from './transports/transport-types';
export * from './transports/websocket/websocket-transport';
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,10 +1,10 @@
import { IRpcApi, IRpcSubscriptionsApi, IRpcTransport } from '@solana/rpc-types';
import { IIRpcTransport, IRpcApi, IRpcSubscriptionsApi } from '@solana/rpc-types';

import { IRpcWebSocketTransport } from './transports/transport-types';

export type RpcConfig<TRpcMethods> = Readonly<{
api: IRpcApi<TRpcMethods>;
transport: IRpcTransport;
transport: IIRpcTransport;
}>;

export type RpcSubscriptionConfig<TRpcMethods> = Readonly<{
Expand Down
46 changes: 43 additions & 3 deletions packages/rpc-transport/src/json-rpc.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,18 @@
import { PendingRpcRequest, Rpc, RpcRequest, SendOptions } from '@solana/rpc-types';
import {
IRpcApi,
IRpcTransport,
IRpcTransportDevnet,
IRpcTransportMainnet,
IRpcTransportTestnet,
PendingRpcRequest,
Rpc,
RpcDevnet,
RpcFromTransport,
RpcMainnet,
RpcRequest,
RpcTestnet,
SendOptions,
} from '@solana/rpc-types';

import { RpcConfig } from './json-rpc-config';
import { SolanaJsonRpcError } from './json-rpc-errors';
Expand Down Expand Up @@ -54,6 +68,32 @@ function makeProxy<TRpcMethods>(rpcConfig: RpcConfig<TRpcMethods>): Rpc<TRpcMeth
}) as Rpc<TRpcMethods>;
}

export function createJsonRpc<TRpcMethods>(rpcConfig: RpcConfig<TRpcMethods>): Rpc<TRpcMethods> {
return makeProxy(rpcConfig);
export function createJsonRpc<TRpcMethods>(
rpcConfig: Readonly<{
api: IRpcApi<TRpcMethods>;
transport: IRpcTransportDevnet;
}>,
): RpcDevnet<TRpcMethods>;
export function createJsonRpc<TRpcMethods>(
rpcConfig: Readonly<{
api: IRpcApi<TRpcMethods>;
transport: IRpcTransportTestnet;
}>,
): RpcTestnet<TRpcMethods>;
export function createJsonRpc<TRpcMethods>(
rpcConfig: Readonly<{
api: IRpcApi<TRpcMethods>;
transport: IRpcTransportMainnet;
}>,
): RpcMainnet<TRpcMethods>;
export function createJsonRpc<TRpcMethods>(
rpcConfig: Readonly<{
api: IRpcApi<TRpcMethods>;
transport: IRpcTransport;
}>,
): Rpc<TRpcMethods>;
export function createJsonRpc<TRpcMethods, TConfig extends RpcConfig<TRpcMethods>>(
rpcConfig: TConfig,
): RpcFromTransport<TRpcMethods, TConfig['transport']> {
return makeProxy(rpcConfig) as RpcFromTransport<TRpcMethods, TConfig['transport']>;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
import {
devnet,
IRpcTransport,
IRpcTransportDevnet,
IRpcTransportMainnet,
IRpcTransportTestnet,
mainnet,
testnet,
} from '@solana/rpc-types';

import { createHttpTransport } from '../http-transport';

const genericUrl = 'http://localhost:8899';
const devnetUrl = devnet('https://api.devnet.solana.com');
const testnetUrl = testnet('https://api.testnet.solana.com');
const mainnetUrl = mainnet('https://api.mainnet-beta.solana.com');

// When providing a generic URL, the transport should be typed as an IRpcTransport
createHttpTransport({ url: genericUrl }) satisfies IRpcTransport;
//@ts-expect-error Should not be a devnet transport
createHttpTransport({ url: genericUrl }) satisfies IRpcTransportDevnet;
//@ts-expect-error Should not be a testnet transport
createHttpTransport({ url: genericUrl }) satisfies IRpcTransportTestnet;
//@ts-expect-error Should not be a mainnet transport
createHttpTransport({ url: genericUrl }) satisfies IRpcTransportMainnet;

// When providing a devnet URL, the transport should be typed as an IRpcTransportDevnet
createHttpTransport({ url: devnetUrl }) satisfies IRpcTransportDevnet;
//@ts-expect-error Should not be a testnet transport
createHttpTransport({ url: devnetUrl }) satisfies IRpcTransportTestnet;
//@ts-expect-error Should not be a mainnet transport
createHttpTransport({ url: devnetUrl }) satisfies IRpcTransportMainnet;

// When providing a testnet URL, the transport should be typed as an IRpcTransportTestnet
createHttpTransport({ url: testnetUrl }) satisfies IRpcTransportTestnet;
//@ts-expect-error Should not be a devnet transport
createHttpTransport({ url: testnetUrl }) satisfies IRpcTransportDevnet;
//@ts-expect-error Should not be a mainnet transport
createHttpTransport({ url: testnetUrl }) satisfies IRpcTransportMainnet;

// When providing a mainnet URL, the transport should be typed as an IRpcTransportMainnet
createHttpTransport({ url: mainnetUrl }) satisfies IRpcTransportMainnet;
//@ts-expect-error Should not be a devnet transport
createHttpTransport({ url: mainnetUrl }) satisfies IRpcTransportDevnet;
//@ts-expect-error Should not be a testnet transport
createHttpTransport({ url: mainnetUrl }) satisfies IRpcTransportTestnet;
11 changes: 7 additions & 4 deletions packages/rpc-transport/src/transports/http/http-transport.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { IRpcTransport } from '@solana/rpc-types';
import { ClusterUrl, IRpcTransport, IRpcTransportFromClusterUrl } from '@solana/rpc-types';
import fetchImpl from 'fetch-impl';

import { SolanaHttpError } from './http-transport-errors';
Expand All @@ -10,10 +10,13 @@ import {

type Config = Readonly<{
headers?: AllowedHttpRequestHeaders;
url: string;
url: ClusterUrl;
}>;

export function createHttpTransport({ headers, url }: Config): IRpcTransport {
export function createHttpTransport<TConfig extends Config>({
headers,
url,
}: TConfig): IRpcTransportFromClusterUrl<TConfig['url']> {
if (__DEV__ && headers) {
assertIsAllowedHttpRequestHeaders(headers);
}
Expand Down Expand Up @@ -43,5 +46,5 @@ export function createHttpTransport({ headers, url }: Config): IRpcTransport {
});
}
return (await response.json()) as TResponse;
};
} as IRpcTransportFromClusterUrl<TConfig['url']>;
}
27 changes: 27 additions & 0 deletions packages/rpc-types/src/rpc-api.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import { ClusterUrl, DevnetUrl, MainnetUrl, TestnetUrl } from './cluster-url';
import type { Overloads } from './overloads';
import type { Slot } from './typed-numbers';

Expand All @@ -12,6 +13,17 @@ type RpcTransportConfig = Readonly<{
export interface IRpcTransport {
<TResponse>(config: RpcTransportConfig): Promise<TResponse>;
}
export type IRpcTransportDevnet = IRpcTransport & { '~cluster': 'devnet' };
export type IRpcTransportTestnet = IRpcTransport & { '~cluster': 'testnet' };
export type IRpcTransportMainnet = IRpcTransport & { '~cluster': 'mainnet' };
export type IIRpcTransport = IRpcTransport | IRpcTransportDevnet | IRpcTransportTestnet | IRpcTransportMainnet;
export type IRpcTransportFromClusterUrl<TClusterUrl extends ClusterUrl> = TClusterUrl extends DevnetUrl
? IRpcTransportDevnet
: TClusterUrl extends TestnetUrl
? IRpcTransportTestnet
: TClusterUrl extends MainnetUrl
? IRpcTransportMainnet
: IRpcTransport;

/**
* Public RPC API.
Expand All @@ -22,7 +34,22 @@ export type IRpcApi<TRpcMethods> = {
export type IRpcSubscriptionsApi<TRpcSubscriptionMethods> = {
[MethodName in keyof TRpcSubscriptionMethods]: RpcSubscriptionReturnTypeMapper<TRpcSubscriptionMethods[MethodName]>;
};

export type Rpc<TRpcMethods> = RpcMethods<TRpcMethods>;
export type RpcDevnet<TRpcMethods> = RpcMethods<TRpcMethods> & { '~cluster': 'devnet' };
export type RpcTestnet<TRpcMethods> = RpcMethods<TRpcMethods> & { '~cluster': 'testnet' };
export type RpcMainnet<TRpcMethods> = RpcMethods<TRpcMethods> & { '~cluster': 'mainnet' };
export type RpcFromTransport<
TRpcMethods,
TRpcTransport extends IIRpcTransport,
> = TRpcTransport extends IRpcTransportDevnet
? RpcDevnet<TRpcMethods>
: TRpcTransport extends IRpcTransportTestnet
? RpcTestnet<TRpcMethods>
: TRpcTransport extends IRpcTransportMainnet
? RpcMainnet<TRpcMethods>
: Rpc<TRpcMethods>;

export type RpcSubscriptions<TRpcSubscriptionMethods> = RpcSubscriptionMethods<TRpcSubscriptionMethods>;

/**
Expand Down

0 comments on commit 43492b5

Please sign in to comment.