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 Jan 30, 2024
1 parent 495a9b1 commit 3fbed8a
Show file tree
Hide file tree
Showing 6 changed files with 154 additions and 10 deletions.
77 changes: 76 additions & 1 deletion packages/rpc-transport/src/__typetests__/methods-api-typetest.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,19 @@
import { IRpcApi, IRpcApiMethods } from '@solana/rpc-types';
import {
IRpcApi,
IRpcApiDevnet,
IRpcApiMainnet,
IRpcApiMethods,
IRpcApiTestnet,
Rpc,
RpcDevnet,
RpcMainnet,
RpcTestnet,
} from '@solana/rpc-types';

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

// Custom API type tests
type NftCollectionDetailsApiResponse = Readonly<{
address: string;
circulatingSupply: number;
Expand All @@ -21,3 +33,66 @@ interface NftCollectionDetailsApi extends IRpcApiMethods {
type QuickNodeRpcMethods = NftCollectionDetailsApi;

createJsonRpcApi<QuickNodeRpcMethods>() satisfies IRpcApi<QuickNodeRpcMethods>;

const configApi = null as unknown as Omit<Parameters<typeof createJsonRpcApi>[0], 'api'>;

// Cluster-level API type tests
interface MyApiMethods extends IRpcApiMethods {
foo(): void;
}

// No cluster specified should be generic `IRpcApi`
createJsonRpcApi<MyApiMethods>(configApi) satisfies IRpcApi<MyApiMethods>;

// When devnet is specified, should be `IRpcApiDevnet`
createJsonRpcApi<MyApiMethods, 'devnet'>(configApi) satisfies IRpcApiDevnet<MyApiMethods>;
// @ts-expect-error Should not be a testnet API
createJsonRpcApi<MyApiMethods, 'devnet'>(configApi) satisfies IRpcApiTestnet<MyApiMethods>;
// @ts-expect-error Should not be a mainnet API
createJsonRpcApi<MyApiMethods, 'devnet'>(configApi) satisfies IRpcApiMainnet<MyApiMethods>;

// When testnet is specified, should be `IRpcApiTestnet`
createJsonRpcApi<MyApiMethods, 'testnet'>(configApi) satisfies IRpcApi<MyApiMethods>;
createJsonRpcApi<MyApiMethods, 'testnet'>(configApi) satisfies IRpcApiTestnet<MyApiMethods>;
// @ts-expect-error Should not be a devnet API
createJsonRpcApi<MyApiMethods, 'testnet'>(configApi) satisfies IRpcApiDevnet<MyApiMethods>;
// @ts-expect-error Should not be a mainnet API
createJsonRpcApi<MyApiMethods, 'testnet'>(configApi) satisfies IRpcApiMainnet<MyApiMethods>;

// When mainnet is specified, should be `IRpcApiMainnet`
createJsonRpcApi<MyApiMethods, 'mainnet'>(configApi) satisfies IRpcApi<MyApiMethods>;
createJsonRpcApi<MyApiMethods, 'mainnet'>(configApi) satisfies IRpcApiMainnet<MyApiMethods>;
// @ts-expect-error Should not be a devnet API
createJsonRpcApi<MyApiMethods, 'mainnet'>(configApi) satisfies IRpcApiDevnet<MyApiMethods>;
// @ts-expect-error Should not be a testnet API
createJsonRpcApi<MyApiMethods, 'mainnet'>(configApi) satisfies IRpcApiTestnet<MyApiMethods>;

const transport = null as unknown as Parameters<typeof createJsonRpc>[0]['transport'];

// When providing a generic API to `createJsonRpc`, the RPC should be a generic RPC
const apiGeneric = createJsonRpcApi<MyApiMethods>(configApi);
createJsonRpc({ api: apiGeneric, transport }) satisfies Rpc<MyApiMethods>;

// When providing a devnet API to `createJsonRpc`, the RPC should be a devnet RPC
const apiDevnet = createJsonRpcApi<MyApiMethods, 'devnet'>(configApi);
createJsonRpc<MyApiMethods>({ api: apiDevnet, transport }) satisfies RpcDevnet<MyApiMethods>;
// @ts-expect-error Should not be a testnet RPC
createJsonRpc({ api: apiDevnet, transport }) satisfies RpcTestnet<MyApiMethods>;
// @ts-expect-error Should not be a mainnet RPC
createJsonRpc({ api: apiDevnet, transport }) satisfies RpcMainnet<MyApiMethods>;

// When providing a testnet API to `createJsonRpc`, the RPC should be a testnet RPC
const apiTestnet = createJsonRpcApi<MyApiMethods, 'testnet'>(configApi);
createJsonRpc({ api: apiTestnet, transport }) satisfies RpcTestnet<MyApiMethods>;
// @ts-expect-error Should not be a devnet RPC
createJsonRpc({ api: apiTestnet, transport }) satisfies RpcDevnet<MyApiMethods>;
// @ts-expect-error Should not be a mainnet RPC
createJsonRpc({ api: apiTestnet, transport }) satisfies RpcMainnet<MyApiMethods>;

// When providing a mainnet API to `createJsonRpc`, the RPC should be a mainnet RPC
const apiMainnet = createJsonRpcApi<MyApiMethods, 'mainnet'>(configApi);
createJsonRpc({ api: apiMainnet, transport }) satisfies RpcMainnet<MyApiMethods>;
// @ts-expect-error Should not be a devnet RPC
createJsonRpc({ api: apiMainnet, transport }) satisfies RpcDevnet<MyApiMethods>;
// @ts-expect-error Should not be a testnet RPC
createJsonRpc({ api: apiMainnet, transport }) satisfies RpcTestnet<MyApiMethods>;
9 changes: 6 additions & 3 deletions packages/rpc-transport/src/apis/methods/methods-api.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,11 @@
import { IRpcApi, IRpcApiMethods, RpcRequest } from '@solana/rpc-types';
import { IRpcApi, IRpcApiFromCluster, IRpcApiMethods, RpcRequest } from '@solana/rpc-types';

import { RpcApiConfig } from '../api-types';

export function createJsonRpcApi<TRpcMethods extends IRpcApiMethods>(config?: RpcApiConfig): IRpcApi<TRpcMethods> {
export function createJsonRpcApi<
TRpcMethods extends IRpcApiMethods,
TCluster extends 'devnet' | 'testnet' | 'mainnet' | void = void,
>(config?: RpcApiConfig): IRpcApiFromCluster<TRpcMethods, TCluster> {
return new Proxy({} as IRpcApi<TRpcMethods>, {
defineProperty() {
return false;
Expand Down Expand Up @@ -33,5 +36,5 @@ export function createJsonRpcApi<TRpcMethods extends IRpcApiMethods>(config?: Rp
};
};
},
});
}) as IRpcApiFromCluster<TRpcMethods, TCluster>;
}
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 { IRpcTransport, 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,9 +1,9 @@
import { IRpcApi, IRpcSubscriptionsApi } from '@solana/rpc-types';
import { IRpcApi, IRpcApiDevnet, IRpcApiMainnet, IRpcApiTestnet, IRpcSubscriptionsApi } from '@solana/rpc-types';

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

export type RpcConfig<TRpcMethods> = Readonly<{
api: IRpcApi<TRpcMethods>;
api: IRpcApi<TRpcMethods> | IRpcApiDevnet<TRpcMethods> | IRpcApiTestnet<TRpcMethods> | IRpcApiMainnet<TRpcMethods>;
transport: IRpcTransport;
}>;

Expand Down
38 changes: 35 additions & 3 deletions packages/rpc-transport/src/json-rpc.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,22 @@
import { PendingRpcRequest, Rpc, RpcRequest, SendOptions } from '@solana/rpc-types';
import {
IRpcApi,
IRpcApiDevnet,
IRpcApiMainnet,
IRpcApiTestnet,
PendingRpcRequest,
Rpc,
RpcDevnet,
RpcFromConfig,
RpcMainnet,
RpcRequest,
RpcTestnet,
SendOptions,
} from '@solana/rpc-types';

import { RpcConfig } from './json-rpc-config';
import { SolanaJsonRpcError } from './json-rpc-errors';
import { createJsonRpcMessage } from './json-rpc-message';
import { IRpcTransport } from './transports/transport-types';

interface IHasIdentifier {
readonly id: number;
Expand Down Expand Up @@ -54,6 +68,24 @@ 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: {
api: IRpcApiDevnet<TRpcMethods>;
transport: IRpcTransport;
}): RpcDevnet<TRpcMethods>;
export function createJsonRpc<TRpcMethods>(rpcConfig: {
api: IRpcApiTestnet<TRpcMethods>;
transport: IRpcTransport;
}): RpcTestnet<TRpcMethods>;
export function createJsonRpc<TRpcMethods>(rpcConfig: {
api: IRpcApiMainnet<TRpcMethods>;
transport: IRpcTransport;
}): RpcMainnet<TRpcMethods>;
export function createJsonRpc<TRpcMethods>(rpcConfig: {
api: IRpcApi<TRpcMethods>;
transport: IRpcTransport;
}): Rpc<TRpcMethods>;
export function createJsonRpc<TRpcMethods>(
rpcConfig: RpcConfig<TRpcMethods>,
): RpcFromConfig<TRpcMethods, RpcConfig<TRpcMethods>> {
return makeProxy(rpcConfig) as RpcFromConfig<TRpcMethods, RpcConfig<TRpcMethods>>;
}
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 @@ -7,10 +7,45 @@ import type { Slot } from './typed-numbers';
export type IRpcApi<TRpcMethods> = {
[MethodName in keyof TRpcMethods]: RpcReturnTypeMapper<TRpcMethods[MethodName]>;
};
export type IRpcApiDevnet<TRpcMethods> = IRpcApi<TRpcMethods> & { '~cluster': 'devnet' };
export type IRpcApiTestnet<TRpcMethods> = IRpcApi<TRpcMethods> & { '~cluster': 'testnet' };
export type IRpcApiMainnet<TRpcMethods> = IRpcApi<TRpcMethods> & { '~cluster': 'mainnet' };
export type IRpcApiFromCluster<
TRpcMethods extends IRpcApiMethods,
TCluster extends 'devnet' | 'testnet' | 'mainnet' | void = void,
> = TCluster extends 'devnet'
? IRpcApiDevnet<TRpcMethods>
: TCluster extends 'testnet'
? IRpcApiTestnet<TRpcMethods>
: TCluster extends 'mainnet'
? IRpcApiMainnet<TRpcMethods>
: 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 RpcFromConfig<
TRpcMethods,
TConfig extends Readonly<{
api:
| IRpcApi<TRpcMethods>
| IRpcApiDevnet<TRpcMethods>
| IRpcApiTestnet<TRpcMethods>
| IRpcApiMainnet<TRpcMethods>;
}>,
> = TConfig['api'] extends IRpcApiDevnet<TRpcMethods>
? RpcDevnet<TRpcMethods>
: TConfig['api'] extends IRpcApiTestnet<TRpcMethods>
? RpcTestnet<TRpcMethods>
: TConfig['api'] extends IRpcApiMainnet<TRpcMethods>
? RpcMainnet<TRpcMethods>
: Rpc<TRpcMethods>;

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

/**
Expand Down

0 comments on commit 3fbed8a

Please sign in to comment.