Skip to content

Commit f73bc43

Browse files
committed
Document @solana/rpc-transport-http with TypeDoc
1 parent a495ee4 commit f73bc43

File tree

6 files changed

+300
-32
lines changed

6 files changed

+300
-32
lines changed

Diff for: packages/rpc-transport-http/README.md

+2-2
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@
1111

1212
# @solana/rpc-transport-http
1313

14-
This package allows developers to create custom RPC transports. With this library, one can implement highly specialized functionality for leveraging multiple transports, attempting/handling retries, and more.
14+
This package allows developers to create custom RPC transports. Using these primitives, developers can create custom transports that perform transforms on messages sent and received, attempt retries, and implement routing strategies between multiple transports.
1515

1616
## Functions
1717

@@ -109,7 +109,7 @@ A string representing the target endpoint. In Node, it must be an absolute URL u
109109

110110
### `createHttpTransportForSolanaRpc()`
111111

112-
Creates an `RpcTransport` that uses JSON HTTP requests — much like the `createHttpTransport` function — except that it also uses custom `toJson` and `fromJson` functions in order to allow `bigint` values to be serialized and deserialized correctly over the wire.
112+
Creates a `RpcTransport` that uses JSON HTTP requests — much like the `createHttpTransport` function — except that it also uses custom `toJson` and `fromJson` functions in order to allow `bigint` values to be serialized and deserialized correctly over the wire.
113113

114114
Since this is something specific to the Solana RPC API, these custom JSON functions are only triggered when the request is recognized as a Solana RPC request. Normal RPC APIs should aim to wrap their `bigint` values — e.g. `u64` or `i64` — in special value objects that represent the number as a string to avoid numerical values going above `Number.MAX_SAFE_INTEGER`.
115115

+101
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,101 @@
1+
import { RpcResponse } from '@solana/rpc-spec-types';
2+
import { Dispatcher } from 'undici-types';
3+
4+
import { AllowedHttpRequestHeaders } from './http-transport-headers';
5+
6+
export type HttpTransportConfig = Readonly<{
7+
/**
8+
* In Node environments you can tune how requests are dispatched to the network. Use this config
9+
* parameter to install a
10+
* [`undici.Dispatcher`](https://undici.nodejs.org/#/docs/api/Agent) in your transport.
11+
*
12+
* @example
13+
* ```ts
14+
* import { createHttpTransport } from '@solana/rpc-transport-http';
15+
* import { Agent, BalancedPool } from 'undici';
16+
*
17+
* // Create a dispatcher that, when called with a special URL, creates a round-robin pool of RPCs.
18+
* const dispatcher = new Agent({
19+
* factory(origin, opts) {
20+
* if (origin === 'https://mypool') {
21+
* const upstreams = [
22+
* 'https://api.mainnet-beta.solana.com',
23+
* 'https://mainnet.helius-rpc.com',
24+
* 'https://several-neat-iguana.quiknode.pro',
25+
* ];
26+
* return new BalancedPool(upstreams, {
27+
* ...opts,
28+
* bodyTimeout: 60e3,
29+
* headersTimeout: 5e3,
30+
* keepAliveTimeout: 19e3,
31+
* });
32+
* } else {
33+
* return new Pool(origin, opts);
34+
* }
35+
* },
36+
* });
37+
* const transport = createHttpTransport({
38+
* dispatcher_NODE_ONLY: dispatcher,
39+
* url: 'https://mypool',
40+
* });
41+
* let id = 0;
42+
* const balances = await Promise.allSettled(
43+
* accounts.map(async account => {
44+
* const response = await transport({
45+
* payload: {
46+
* id: ++id,
47+
* jsonrpc: '2.0',
48+
* method: 'getBalance',
49+
* params: [account],
50+
* },
51+
* });
52+
* return await response.json();
53+
* }),
54+
* );
55+
* ```
56+
*/
57+
dispatcher_NODE_ONLY?: Dispatcher;
58+
/**
59+
* An optional function that takes the response as a JSON string and converts it to a JSON
60+
* value.
61+
*
62+
* The request payload is also provided as a second argument.
63+
*
64+
* @defaultValue When not provided, the JSON value will be accessed via the `response.json()`
65+
* method of the fetch API.
66+
*/
67+
fromJson?: (rawResponse: string, payload: unknown) => RpcResponse;
68+
/**
69+
* An object of headers to set on the request.
70+
*
71+
* Avoid
72+
* [forbidden headers](https://developer.mozilla.org/en-US/docs/Glossary/Forbidden_header_name).
73+
* Additionally, the headers `Accept`, `Content-Length`, and `Content-Type` are disallowed.
74+
*
75+
* @example
76+
* ```ts
77+
* import { createHttpTransport } from '@solana/rpc-transport-http';
78+
*
79+
* const transport = createHttpTransport({
80+
* headers: {
81+
* // Authorize with the RPC using a bearer token
82+
* Authorization: `Bearer ${process.env.RPC_AUTH_TOKEN}`,
83+
* },
84+
* url: 'https://several-neat-iguana.quiknode.pro',
85+
* });
86+
* ```
87+
*/
88+
headers?: AllowedHttpRequestHeaders;
89+
/**
90+
* An optional function that takes the request payload and converts it to a JSON string.
91+
*
92+
* @defaultValue When not provided, `JSON.stringify` will be used.
93+
*/
94+
toJson?: (payload: unknown) => string;
95+
/**
96+
* A string representing the target endpoint.
97+
*
98+
* In Node, it must be an absolute URL using the `http` or `https` protocol.
99+
*/
100+
url: string;
101+
}>;

Diff for: packages/rpc-transport-http/src/http-transport-for-solana-rpc.ts

+16-7
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,26 @@
11
import { RpcTransport } from '@solana/rpc-spec';
22
import { parseJsonWithBigInts, stringifyJsonWithBigints } from '@solana/rpc-spec-types';
3-
import type Dispatcher from 'undici-types/dispatcher';
43

54
import { createHttpTransport } from './http-transport';
6-
import { AllowedHttpRequestHeaders } from './http-transport-headers';
5+
import { HttpTransportConfig } from './http-transport-config';
76
import { isSolanaRequest } from './is-solana-request';
87

9-
type Config = Readonly<{
10-
dispatcher_NODE_ONLY?: Dispatcher;
11-
headers?: AllowedHttpRequestHeaders;
12-
url: string;
13-
}>;
8+
type Config = Pick<HttpTransportConfig, 'dispatcher_NODE_ONLY' | 'headers' | 'url'>;
149

10+
/**
11+
* Creates a {@link RpcTransport} that uses JSON HTTP requests — much like the
12+
* {@link createHttpTransport} function - except that it also uses custom `toJson` and `fromJson`
13+
* functions in order to allow `bigint` values to be serialized and deserialized correctly over the
14+
* wire.
15+
*
16+
* Since this is something specific to the Solana RPC API, these custom JSON functions are only
17+
* triggered when the request is recognized as a Solana RPC request. Normal RPC APIs should aim to
18+
* wrap their `bigint` values — e.g. `u64` or `i64` — in special value objects that represent the
19+
* number as a string to avoid numerical values going above `Number.MAX_SAFE_INTEGER`.
20+
*
21+
* It has the same configuration options as {@link createHttpTransport}, but without the `fromJson`
22+
* and `toJson` options.
23+
*/
1524
export function createHttpTransportForSolanaRpc(config: Config): RpcTransport {
1625
return createHttpTransport({
1726
...config,

Diff for: packages/rpc-transport-http/src/http-transport-headers.ts

+6-10
Original file line numberDiff line numberDiff line change
@@ -18,12 +18,10 @@ export type AllowedHttpRequestHeaders = Readonly<
1818
type DisallowedHeaders = 'Accept' | 'Content-Length' | 'Content-Type' | 'Solana-Client';
1919
type ForbiddenHeaders =
2020
| 'Accept-Charset'
21-
/**
22-
* Though technically forbidden in non-Node environments, we don't have a way to target
23-
* TypeScript types depending on which platform you are authoring for. `Accept-Encoding` is
24-
* therefore omitted from the forbidden headers type, but is still a runtime error in dev mode
25-
* when supplied in a non-Node context.
26-
*/
21+
// Though technically forbidden in non-Node environments, we don't have a way to target
22+
// TypeScript types depending on which platform you are authoring for. `Accept-Encoding` is
23+
// therefore omitted from the forbidden headers type, but is still a runtime error in dev mode
24+
// when supplied in a non-Node context.
2725
// | 'Accept-Encoding'
2826
| 'Access-Control-Request-Headers'
2927
| 'Access-Control-Request-Method'
@@ -100,10 +98,8 @@ export function assertIsAllowedHttpRequestHeaders(
10098
}
10199
}
102100

103-
/**
104-
* Lowercasing header names makes it easier to override user-supplied headers, such as those defined
105-
* in the `DisallowedHeaders` type.
106-
*/
101+
// Lowercasing header names makes it easier to override user-supplied headers, such as those defined
102+
// in the `DisallowedHeaders` type.
107103
export function normalizeHeaders<T extends Record<string, string>>(
108104
headers: T,
109105
): { [K in string & keyof T as Lowercase<K>]: T[K] } {

Diff for: packages/rpc-transport-http/src/http-transport.ts

+17-13
Original file line numberDiff line numberDiff line change
@@ -3,19 +3,8 @@ import type { RpcTransport } from '@solana/rpc-spec';
33
import type { RpcResponse } from '@solana/rpc-spec-types';
44
import type Dispatcher from 'undici-types/dispatcher';
55

6-
import {
7-
AllowedHttpRequestHeaders,
8-
assertIsAllowedHttpRequestHeaders,
9-
normalizeHeaders,
10-
} from './http-transport-headers';
11-
12-
type Config = Readonly<{
13-
dispatcher_NODE_ONLY?: Dispatcher;
14-
fromJson?: (rawResponse: string, payload: unknown) => RpcResponse;
15-
headers?: AllowedHttpRequestHeaders;
16-
toJson?: (payload: unknown) => string;
17-
url: string;
18-
}>;
6+
import { HttpTransportConfig as Config } from './http-transport-config';
7+
import { assertIsAllowedHttpRequestHeaders, normalizeHeaders } from './http-transport-headers';
198

209
let didWarnDispatcherWasSuppliedInNonNodeEnvironment = false;
2110
function warnDispatcherWasSuppliedInNonNodeEnvironment() {
@@ -31,6 +20,21 @@ function warnDispatcherWasSuppliedInNonNodeEnvironment() {
3120
);
3221
}
3322

23+
/**
24+
* Creates a function you can use to make `POST` requests with headers suitable for sending JSON
25+
* data to a server.
26+
*
27+
* @example
28+
* ```ts
29+
* import { createHttpTransport } from '@solana/rpc-transport-http';
30+
*
31+
* const transport = createHttpTransport({ url: 'https://api.mainnet-beta.solana.com' });
32+
* const response = await transport({
33+
* payload: { id: 1, jsonrpc: '2.0', method: 'getSlot' },
34+
* });
35+
* const data = await response.json();
36+
* ```
37+
*/
3438
export function createHttpTransport(config: Config): RpcTransport {
3539
if (__DEV__ && !__NODEJS__ && 'dispatcher_NODE_ONLY' in config) {
3640
warnDispatcherWasSuppliedInNonNodeEnvironment();

Diff for: packages/rpc-transport-http/src/index.ts

+158
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,160 @@
1+
/**
2+
* This package allows developers to create custom RPC transports. Using these primitives,
3+
* developers can create custom transports that perform transforms on messages sent and received,
4+
* attempt retries, and implement routing strategies between multiple transports.
5+
*
6+
* ## Augmenting Transports
7+
*
8+
* Using this core transport, you can implement specialized functionality for leveraging multiple
9+
* transports, attempting/handling retries, and more.
10+
*
11+
* ### Round Robin
12+
*
13+
* Here’s an example of how someone might implement a “round robin” approach to distribute requests
14+
* to multiple transports:
15+
*
16+
* ```ts
17+
* import { RpcTransport } from '@solana/rpc-spec';
18+
* import { RpcResponse } from '@solana/rpc-spec-types';
19+
* import { createHttpTransport } from '@solana/rpc-transport-http';
20+
*
21+
* // Create a transport for each RPC server
22+
* const transports = [
23+
* createHttpTransport({ url: 'https://mainnet-beta.my-server-1.com' }),
24+
* createHttpTransport({ url: 'https://mainnet-beta.my-server-2.com' }),
25+
* createHttpTransport({ url: 'https://mainnet-beta.my-server-3.com' }),
26+
* ];
27+
*
28+
* // Create a wrapper transport that distributes requests to them
29+
* let nextTransport = 0;
30+
* async function roundRobinTransport<TResponse>(...args: Parameters<RpcTransport>): Promise<RpcResponse<TResponse>> {
31+
* const transport = transports[nextTransport];
32+
* nextTransport = (nextTransport + 1) % transports.length;
33+
* return await transport(...args);
34+
* }
35+
* ```
36+
*
37+
* ### Sharding
38+
*
39+
* Another example of a possible customization for a transport is to shard requests
40+
* deterministically among a set of servers. Here’s an example:
41+
*
42+
* Perhaps your application needs to make a large number of requests, or needs to fan request for
43+
* different methods out to different servers. Here’s an example of an implementation that does the
44+
* latter:
45+
*
46+
* ```ts
47+
* import { RpcTransport } from '@solana/rpc-spec';
48+
* import { RpcResponse } from '@solana/rpc-spec-types';
49+
* import { createHttpTransport } from '@solana/rpc-transport-http';
50+
*
51+
* // Create multiple transports
52+
* const transportA = createHttpTransport({ url: 'https://mainnet-beta.my-server-1.com' });
53+
* const transportB = createHttpTransport({ url: 'https://mainnet-beta.my-server-2.com' });
54+
* const transportC = createHttpTransport({ url: 'https://mainnet-beta.my-server-3.com' });
55+
* const transportD = createHttpTransport({ url: 'https://mainnet-beta.my-server-4.com' });
56+
*
57+
* // Function to determine which shard to use based on the request method
58+
* function selectShard(method: string): RpcTransport {
59+
* switch (method) {
60+
* case 'getAccountInfo':
61+
* case 'getBalance':
62+
* return transportA;
63+
* case 'getLatestBlockhash':
64+
* case 'getTransaction':
65+
* return transportB;
66+
* case 'sendTransaction':
67+
* return transportC;
68+
* default:
69+
* return transportD;
70+
* }
71+
* }
72+
*
73+
* async function shardingTransport<TResponse>(...args: Parameters<RpcTransport>): Promise<RpcResponse<TResponse>> {
74+
* const payload = args[0].payload as { method: string };
75+
* const selectedTransport = selectShard(payload.method);
76+
* return await selectedTransport(...args);
77+
* }
78+
* ```
79+
*
80+
* ### Retry Logic
81+
*
82+
* The transport library can also be used to implement custom retry logic on any request:
83+
*
84+
* ```ts
85+
* import { RpcTransport } from '@solana/rpc-spec';
86+
* import { RpcResponse } from '@solana/rpc-spec-types';
87+
* import { createHttpTransport } from '@solana/rpc-transport-http';
88+
*
89+
* // Set the maximum number of attempts to retry a request
90+
* const MAX_ATTEMPTS = 4;
91+
*
92+
* // Create the default transport
93+
* const defaultTransport = createHttpTransport({ url: 'https://mainnet-beta.my-server-1.com' });
94+
*
95+
* // Sleep function to wait for a given number of milliseconds
96+
* function sleep(ms: number): Promise<void> {
97+
* return new Promise(resolve => setTimeout(resolve, ms));
98+
* }
99+
*
100+
* // Calculate the delay for a given attempt
101+
* function calculateRetryDelay(attempt: number): number {
102+
* // Exponential backoff with a maximum of 1.5 seconds
103+
* return Math.min(100 * Math.pow(2, attempt), 1500);
104+
* }
105+
*
106+
* // A retrying transport that will retry up to `MAX_ATTEMPTS` times before failing
107+
* async function retryingTransport<TResponse>(...args: Parameters<RpcTransport>): Promise<RpcResponse<TResponse>> {
108+
* let requestError;
109+
* for (let attempts = 0; attempts < MAX_ATTEMPTS; attempts++) {
110+
* try {
111+
* return await defaultTransport(...args);
112+
* } catch (err) {
113+
* requestError = err;
114+
* // Only sleep if we have more attempts remaining
115+
* if (attempts < MAX_ATTEMPTS - 1) {
116+
* const retryDelay = calculateRetryDelay(attempts);
117+
* await sleep(retryDelay);
118+
* }
119+
* }
120+
* }
121+
* throw requestError;
122+
* }
123+
* ```
124+
*
125+
* ### Failover
126+
*
127+
* Here’s an example of some failover logic integrated into a transport:
128+
*
129+
* ```ts
130+
* import { RpcTransport } from '@solana/rpc-spec';
131+
* import { RpcResponse } from '@solana/rpc-spec-types';
132+
* import { createHttpTransport } from '@solana/rpc-transport-http';
133+
*
134+
* // Create a transport for each RPC server
135+
* const transports = [
136+
* createHttpTransport({ url: 'https://mainnet-beta.my-server-1.com' }),
137+
* createHttpTransport({ url: 'https://mainnet-beta.my-server-2.com' }),
138+
* createHttpTransport({ url: 'https://mainnet-beta.my-server-2.com' }),
139+
* ];
140+
*
141+
* // A failover transport that will try each transport in order until one succeeds before failing
142+
* async function failoverTransport<TResponse>(...args: Parameters<RpcTransport>): Promise<RpcResponse<TResponse>> {
143+
* let requestError;
144+
*
145+
* for (const transport of transports) {
146+
* try {
147+
* return await transport(...args);
148+
* } catch (err) {
149+
* requestError = err;
150+
* console.error(err);
151+
* }
152+
* }
153+
* throw requestError;
154+
* }
155+
* ```
156+
*
157+
* @packageDocumentation
158+
*/
1159
export * from './http-transport';
2160
export * from './http-transport-for-solana-rpc';

0 commit comments

Comments
 (0)