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 25175d9 commit d6e8247
Show file tree
Hide file tree
Showing 16 changed files with 170 additions and 28 deletions.
3 changes: 1 addition & 2 deletions packages/rpc-transport/src/__tests__/json-rpc-test.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,9 @@
import { IRpcApi, Rpc, RpcRequest } from '@solana/rpc-types';
import { IRpcApi, IRpcTransport, Rpc, RpcRequest } from '@solana/rpc-types';

import { createJsonRpc } from '../json-rpc';
import { SolanaJsonRpcError } from '../json-rpc-errors';
import { createJsonRpcMessage } from '../json-rpc-message';
import { getNextMessageId } from '../json-rpc-message-id';
import { IRpcTransport } from '../transports/transport-types';

jest.mock('../json-rpc-message-id');

Expand Down
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.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
3 changes: 1 addition & 2 deletions 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 type { IRpcWebSocketTransport } from './transports/transport-types';
export * from './transports/websocket/websocket-transport';
6 changes: 3 additions & 3 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 } from '@solana/rpc-types';
import { IIRpcTransport, IRpcApi, IRpcSubscriptionsApi } from '@solana/rpc-types';

import { IRpcTransport, IRpcWebSocketTransport } from './transports/transport-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
8 changes: 5 additions & 3 deletions packages/rpc-transport/src/json-rpc.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { PendingRpcRequest, Rpc, RpcRequest, SendOptions } from '@solana/rpc-types';
import { PendingRpcRequest, Rpc, RpcFromTransport, RpcRequest, SendOptions } from '@solana/rpc-types';

import { RpcConfig } from './json-rpc-config';
import { SolanaJsonRpcError } from './json-rpc-errors';
Expand Down Expand Up @@ -54,6 +54,8 @@ 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, 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
@@ -1,6 +1,6 @@
import { IRpcTransport } from '@solana/rpc-types';
import fetchMock from 'jest-fetch-mock-fork';

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

describe('createHttpTransport and `AbortSignal`', () => {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { IRpcTransport } from '../../transport-types';
import { IRpcTransport } from '@solana/rpc-types';

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

Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { IRpcTransport } from '@solana/rpc-types';
import fetchMock from 'jest-fetch-mock-fork';

import { IRpcTransport } from '../../transport-types';
import { createHttpTransport } from '../http-transport';
import { SolanaHttpError } from '../http-transport-errors';

Expand Down
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.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,6 +1,6 @@
import { ClusterUrl, IRpcTransport, IRpcTransportFromClusterUrl } from '@solana/rpc-types';
import fetchImpl from 'fetch-impl';

import { IRpcTransport } from '../transport-types';
import { SolanaHttpError } from './http-transport-errors';
import {
AllowedHttpRequestHeaders,
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']>;
}
9 changes: 0 additions & 9 deletions packages/rpc-transport/src/transports/transport-types.ts
Original file line number Diff line number Diff line change
@@ -1,14 +1,5 @@
import { RpcWebSocketConnection } from './websocket/websocket-connection';

type RpcTransportConfig = Readonly<{
payload: unknown;
signal?: AbortSignal;
}>;

export interface IRpcTransport {
<TResponse>(config: RpcTransportConfig): Promise<TResponse>;
}

type RpcWebSocketTransportConfig = Readonly<{
payload: unknown;
signal: AbortSignal;
Expand Down
14 changes: 14 additions & 0 deletions packages/rpc-types/src/cluster-url.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
export type MainnetUrl = string & { '~cluster': 'mainnet' };
export type DevnetUrl = string & { '~cluster': 'devnet' };
export type TestnetUrl = string & { '~cluster': 'testnet' };
export type ClusterUrl = string | MainnetUrl | DevnetUrl | TestnetUrl;

export function mainnet(putativeString: string): MainnetUrl {
return putativeString as MainnetUrl;
}
export function devnet(putativeString: string): DevnetUrl {
return putativeString as DevnetUrl;
}
export function testnet(putativeString: string): TestnetUrl {
return putativeString as TestnetUrl;
}
1 change: 1 addition & 0 deletions packages/rpc-types/src/index.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
export * from './blockhash';
export * from './cluster-url';
export * from './commitment';
export * from './encoded-bytes';
export * from './lamports';
Expand Down
39 changes: 39 additions & 0 deletions packages/rpc-types/src/rpc-api.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,30 @@
import { ClusterUrl, DevnetUrl, MainnetUrl, TestnetUrl } from './cluster-url';
import type { Overloads } from './overloads';
import type { Slot } from './typed-numbers';

type RpcTransportConfig = Readonly<{
payload: unknown;
signal?: AbortSignal;
}>;

/**
* Public RPC Transport API
*/
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 @@ -10,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 d6e8247

Please sign in to comment.