@solana/rpc-subscriptions 2.0.0-rc.0 → 2.0.0-rc.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (38) hide show
  1. package/README.md +49 -4
  2. package/dist/index.browser.cjs +268 -283
  3. package/dist/index.browser.cjs.map +1 -1
  4. package/dist/index.browser.mjs +267 -285
  5. package/dist/index.browser.mjs.map +1 -1
  6. package/dist/index.native.mjs +250 -274
  7. package/dist/index.native.mjs.map +1 -1
  8. package/dist/index.node.cjs +251 -272
  9. package/dist/index.node.cjs.map +1 -1
  10. package/dist/index.node.mjs +250 -274
  11. package/dist/index.node.mjs.map +1 -1
  12. package/dist/types/index.d.ts +2 -0
  13. package/dist/types/index.d.ts.map +1 -1
  14. package/dist/types/rpc-subscriptions-autopinger.d.ts +5 -4
  15. package/dist/types/rpc-subscriptions-autopinger.d.ts.map +1 -1
  16. package/dist/types/rpc-subscriptions-channel-pool-internal.d.ts +13 -0
  17. package/dist/types/rpc-subscriptions-channel-pool-internal.d.ts.map +1 -0
  18. package/dist/types/rpc-subscriptions-channel-pool.d.ts +8 -0
  19. package/dist/types/rpc-subscriptions-channel-pool.d.ts.map +1 -0
  20. package/dist/types/rpc-subscriptions-channel.d.ts +12 -0
  21. package/dist/types/rpc-subscriptions-channel.d.ts.map +1 -0
  22. package/dist/types/rpc-subscriptions-clusters.d.ts +23 -1
  23. package/dist/types/rpc-subscriptions-clusters.d.ts.map +1 -1
  24. package/dist/types/rpc-subscriptions-coalescer.d.ts +2 -9
  25. package/dist/types/rpc-subscriptions-coalescer.d.ts.map +1 -1
  26. package/dist/types/rpc-subscriptions-json-bigint.d.ts +3 -0
  27. package/dist/types/rpc-subscriptions-json-bigint.d.ts.map +1 -0
  28. package/dist/types/rpc-subscriptions-json.d.ts +3 -0
  29. package/dist/types/rpc-subscriptions-json.d.ts.map +1 -0
  30. package/dist/types/rpc-subscriptions-transport.d.ts +6 -12
  31. package/dist/types/rpc-subscriptions-transport.d.ts.map +1 -1
  32. package/dist/types/rpc-subscriptions.d.ts +5 -4
  33. package/dist/types/rpc-subscriptions.d.ts.map +1 -1
  34. package/package.json +24 -10
  35. package/dist/types/cached-abortable-iterable.d.ts +0 -10
  36. package/dist/types/cached-abortable-iterable.d.ts.map +0 -1
  37. package/dist/types/rpc-subscriptions-connection-sharding.d.ts +0 -13
  38. package/dist/types/rpc-subscriptions-connection-sharding.d.ts.map +0 -1
package/README.md CHANGED
@@ -6,12 +6,57 @@
6
6
 
7
7
  [code-style-prettier-image]: https://img.shields.io/badge/code_style-prettier-ff69b4.svg?style=flat-square
8
8
  [code-style-prettier-url]: https://github.com/prettier/prettier
9
- [npm-downloads-image]: https://img.shields.io/npm/dm/@solana/rpc-subscriptions/experimental.svg?style=flat
10
- [npm-image]: https://img.shields.io/npm/v/@solana/rpc-subscriptions/experimental.svg?style=flat
11
- [npm-url]: https://www.npmjs.com/package/@solana/rpc-subscriptions/v/experimental
9
+ [npm-downloads-image]: https://img.shields.io/npm/dm/@solana/rpc-subscriptions/rc.svg?style=flat
10
+ [npm-image]: https://img.shields.io/npm/v/@solana/rpc-subscriptions/rc.svg?style=flat
11
+ [npm-url]: https://www.npmjs.com/package/@solana/rpc-subscriptions/v/rc
12
12
  [semantic-release-image]: https://img.shields.io/badge/%20%20%F0%9F%93%A6%F0%9F%9A%80-semantic--release-e10079.svg
13
13
  [semantic-release-url]: https://github.com/semantic-release/semantic-release
14
14
 
15
15
  # @solana/rpc-subscriptions
16
16
 
17
- TODO
17
+ This package contains types that implement RPC subscriptions as required by the Solana RPC. Additionally, it incorporates some useful defaults that make working with subscriptions easier, more performant, and more reliable. It can be used standalone, but it is also exported as part of the Solana JavaScript SDK [`@solana/web3.js@rc`](https://github.com/solana-labs/solana-web3.js/tree/master/packages/library).
18
+
19
+ ## Functions
20
+
21
+ ### `createDefaultRpcSubscriptionsChannelCreator(config)`
22
+
23
+ Creates a function that returns new subscription channels when called.
24
+
25
+ ### `createDefaultSolanaRpcSubscriptionsChannelCreator(config)`
26
+
27
+ Similar to `createDefaultRpcSubscriptionsChannelCreator` with some Solana-specific defaults. For instance, it safely handles `BigInt` values in JSON messages since Solana RPC servers accept and return integers larger than `Number.MAX_SAFE_INTEGER`.
28
+
29
+ #### Arguments
30
+
31
+ A config object with the following properties:
32
+
33
+ - `intervalMs`: The number of milliseconds to wait since the last message sent or received over the channel before sending a ping message to keep the channel open.
34
+ - `maxSubscriptionsPerChannel`: The number of subscribers that may share a channel before a new channel must be created (default: 100). It is important that you set this to the maximum number of subscriptions that your RPC provider recommends making over a single connection; the default is set deliberately low, so as to comply with the restrictive limits of the public mainnet RPC node.
35
+ - `minChannels`: The number of channels to create before reusing a channel for a new subscription.
36
+ - `sendBufferHighWatermark`: The number of bytes of data to admint into the `WebSocket` buffer before buffering data on the client. -`url`: The URL of the web socket server. Must use the `ws` or `wss` protocols.
37
+
38
+ ### `getChannelPoolingChannelCreator(createChannel, { maxSubscriptionsPerChannel, minChannels })`
39
+
40
+ Given a channel creator, will return a new channel creator with the following behavior.
41
+
42
+ 1. When called, returns an `RpcSubscriptionsChannel`. Adds that channel to a pool.
43
+ 2. When called again, creates and returns new `RpcSubscriptionChannels` up to the number specified by `minChannels`.
44
+ 3. When `minChannels` channels have been created, subsequent calls vend whichever existing channel from the pool has the fewest subscribers, or the next one in rotation in the event of a tie.
45
+ 4. Once all channels carry the number of subscribers specified by the number `maxSubscriptionsPerChannel`, new channels in excess of `minChannel` will be created, returned, and added to the pool.
46
+ 5. A channel will be destroyed once all of its subscribers' abort signals fire.
47
+
48
+ ### `getRpcSubscriptionsChannelWithJSONSerialization(channel)`
49
+
50
+ Given an `RpcSubscriptionsChannel`, will return a new channel that parses data published to the `'message'` channel as JSON, and JSON-stringifies messages sent via the `send(message)` method.
51
+
52
+ ### `getRpcSubscriptionsChannelWithBigIntJSONSerialization(channel)`
53
+
54
+ Similarly, to `getRpcSubscriptionsChannelWithJSONSerialization`, this function will stringify and parse JSON message to and from the given `string` channel. However, this function parses any integer value as a `BigInt` in order to safely handle numbers that exceed the JavaScript `Number.MAX_SAFE_INTEGER` value.
55
+
56
+ ### `getRpcSubscriptionsChannelWithAutoping(channel)`
57
+
58
+ Given an `RpcSubscriptionsChannel`, will return a new channel that sends a ping message to the inner channel if a message has not been sent or received in the last `intervalMs`. In web browsers, this implementation sends no ping when the network is down, and sends a ping immediately upon the network coming back up.
59
+
60
+ ### `getRpcSubscriptionsTransportWithSubscriptionCoalescing(transport)`
61
+
62
+ Given an `RpcSubscriptionsTransport`, will return a new transport that coalesces identical subscriptions into a single subscription request to the server. The determination of whether a subscription is the same as another is based on the `rpcRequest` returned by its `RpcSubscriptionsPlan`. The subscription will only be aborted once all subscribers abort, or there is an error.
@@ -3,9 +3,10 @@
3
3
  var rpcSubscriptionsApi = require('@solana/rpc-subscriptions-api');
4
4
  var rpcSubscriptionsSpec = require('@solana/rpc-subscriptions-spec');
5
5
  var errors = require('@solana/errors');
6
- var fastStableStringify = require('@solana/fast-stable-stringify');
6
+ var rpcSubscriptionsChannelWebsocket = require('@solana/rpc-subscriptions-channel-websocket');
7
7
  var functional = require('@solana/functional');
8
- var rpcSubscriptionsTransportWebsocket = require('@solana/rpc-subscriptions-transport-websocket');
8
+ var rpcSpecTypes = require('@solana/rpc-spec-types');
9
+ var fastStableStringify = require('@solana/fast-stable-stringify');
9
10
 
10
11
  function _interopDefault (e) { return e && e.__esModule ? e : { default: e }; }
11
12
 
@@ -46,332 +47,316 @@ function createSolanaJsonRpcIntegerOverflowError(methodName, keyPath, value) {
46
47
  // src/rpc-default-config.ts
47
48
  var DEFAULT_RPC_SUBSCRIPTIONS_CONFIG = {
48
49
  defaultCommitment: "confirmed",
49
- onIntegerOverflow(methodName, keyPath, value) {
50
- throw createSolanaJsonRpcIntegerOverflowError(methodName, keyPath, value);
50
+ onIntegerOverflow(request, keyPath, value) {
51
+ throw createSolanaJsonRpcIntegerOverflowError(request.methodName, keyPath, value);
51
52
  }
52
53
  };
53
- function registerIterableCleanup(iterable, cleanupFn) {
54
- (async () => {
55
- try {
56
- for await (const _ of iterable) ;
57
- } catch {
58
- } finally {
59
- cleanupFn();
60
- }
61
- })();
62
- }
63
- function getCachedAbortableIterableFactory({
64
- getAbortSignalFromInputArgs,
65
- getCacheKeyFromInputArgs,
66
- onCacheHit,
67
- onCreateIterable
54
+ var PING_PAYLOAD = {
55
+ jsonrpc: "2.0",
56
+ method: "ping"
57
+ };
58
+ function getRpcSubscriptionsChannelWithAutoping({
59
+ abortSignal: callerAbortSignal,
60
+ channel,
61
+ intervalMs
68
62
  }) {
69
- const cache = /* @__PURE__ */ new Map();
70
- function getCacheEntryOrThrow(cacheKey) {
71
- const currentCacheEntry = cache.get(cacheKey);
72
- if (!currentCacheEntry) {
73
- throw new errors.SolanaError(errors.SOLANA_ERROR__INVARIANT_VIOLATION__CACHED_ABORTABLE_ITERABLE_CACHE_ENTRY_MISSING, {
74
- cacheKey: cacheKey.toString()
75
- });
76
- }
77
- return currentCacheEntry;
78
- }
79
- return async (...args) => {
80
- const cacheKey = getCacheKeyFromInputArgs(...args);
81
- const signal = getAbortSignalFromInputArgs(...args);
82
- if (cacheKey === void 0) {
83
- return await onCreateIterable(signal, ...args);
84
- }
85
- const cleanup = () => {
86
- cache.delete(cacheKey);
87
- signal.removeEventListener("abort", handleAbort);
88
- };
89
- const handleAbort = () => {
90
- const cacheEntry = getCacheEntryOrThrow(cacheKey);
91
- if (cacheEntry.purgeScheduled !== true) {
92
- cacheEntry.purgeScheduled = true;
93
- globalThis.queueMicrotask(() => {
94
- cacheEntry.purgeScheduled = false;
95
- if (cacheEntry.referenceCount === 0) {
96
- cacheEntry.abortController.abort();
97
- cleanup();
98
- }
99
- });
63
+ let intervalId;
64
+ function sendPing() {
65
+ channel.send(PING_PAYLOAD).catch((e) => {
66
+ if (errors.isSolanaError(e, errors.SOLANA_ERROR__RPC_SUBSCRIPTIONS__CHANNEL_CONNECTION_CLOSED)) {
67
+ pingerAbortController.abort();
100
68
  }
101
- cacheEntry.referenceCount--;
102
- };
103
- signal.addEventListener("abort", handleAbort);
104
- try {
105
- const cacheEntry = cache.get(cacheKey);
106
- if (!cacheEntry) {
107
- const singletonAbortController = new AbortController();
108
- const newIterablePromise = onCreateIterable(singletonAbortController.signal, ...args);
109
- const newCacheEntry = {
110
- abortController: singletonAbortController,
111
- iterable: newIterablePromise,
112
- purgeScheduled: false,
113
- referenceCount: 1
114
- };
115
- cache.set(cacheKey, newCacheEntry);
116
- const newIterable = await newIterablePromise;
117
- registerIterableCleanup(newIterable, cleanup);
118
- newCacheEntry.iterable = newIterable;
119
- return newIterable;
120
- } else {
121
- cacheEntry.referenceCount++;
122
- const iterableOrIterablePromise = cacheEntry.iterable;
123
- const cachedIterable = "then" in iterableOrIterablePromise ? await iterableOrIterablePromise : iterableOrIterablePromise;
124
- await onCacheHit(cachedIterable, ...args);
125
- return cachedIterable;
69
+ });
70
+ }
71
+ function restartPingTimer() {
72
+ clearInterval(intervalId);
73
+ intervalId = setInterval(sendPing, intervalMs);
74
+ }
75
+ const pingerAbortController = new AbortController();
76
+ pingerAbortController.signal.addEventListener("abort", () => {
77
+ clearInterval(intervalId);
78
+ });
79
+ callerAbortSignal.addEventListener("abort", () => {
80
+ pingerAbortController.abort();
81
+ });
82
+ channel.on(
83
+ "error",
84
+ () => {
85
+ pingerAbortController.abort();
86
+ },
87
+ { signal: pingerAbortController.signal }
88
+ );
89
+ channel.on("message", restartPingTimer, { signal: pingerAbortController.signal });
90
+ if (globalThis.navigator.onLine) {
91
+ restartPingTimer();
92
+ }
93
+ {
94
+ globalThis.window.addEventListener(
95
+ "offline",
96
+ function handleOffline() {
97
+ clearInterval(intervalId);
98
+ },
99
+ { signal: pingerAbortController.signal }
100
+ );
101
+ globalThis.window.addEventListener(
102
+ "online",
103
+ function handleOnline() {
104
+ sendPing();
105
+ restartPingTimer();
106
+ },
107
+ { signal: pingerAbortController.signal }
108
+ );
109
+ }
110
+ return {
111
+ ...channel,
112
+ send(...args) {
113
+ if (!pingerAbortController.signal.aborted) {
114
+ restartPingTimer();
126
115
  }
127
- } catch (e) {
128
- cleanup();
129
- throw e;
116
+ return channel.send(...args);
130
117
  }
131
118
  };
132
119
  }
133
120
 
134
- // src/rpc-subscriptions-coalescer.ts
135
- var EXPLICIT_ABORT_TOKEN;
136
- function createExplicitAbortToken() {
137
- return Symbol(
138
- process.env.NODE_ENV !== "production" ? "This symbol is thrown from a subscription's iterator when the subscription is explicitly aborted by the user" : void 0
139
- );
121
+ // src/rpc-subscriptions-channel-pool-internal.ts
122
+ function createChannelPool() {
123
+ return {
124
+ entries: [],
125
+ freeChannelIndex: -1
126
+ };
140
127
  }
141
- function registerIterableCleanup2(iterable, cleanupFn) {
142
- (async () => {
143
- try {
144
- for await (const _ of iterable) ;
145
- } catch {
146
- } finally {
147
- cleanupFn();
128
+
129
+ // src/rpc-subscriptions-channel-pool.ts
130
+ function getChannelPoolingChannelCreator(createChannel, { maxSubscriptionsPerChannel, minChannels }) {
131
+ const pool = createChannelPool();
132
+ function recomputeFreeChannelIndex() {
133
+ if (pool.entries.length < minChannels) {
134
+ pool.freeChannelIndex = -1;
135
+ return;
148
136
  }
149
- })();
150
- }
151
- function getRpcSubscriptionsWithSubscriptionCoalescing({
152
- getDeduplicationKey,
153
- rpcSubscriptions
154
- }) {
155
- const cache = /* @__PURE__ */ new Map();
156
- return new Proxy(rpcSubscriptions, {
157
- defineProperty() {
158
- return false;
159
- },
160
- deleteProperty() {
161
- return false;
162
- },
163
- get(target, p, receiver) {
164
- const subscriptionMethod = Reflect.get(target, p, receiver);
165
- if (typeof subscriptionMethod !== "function") {
166
- return subscriptionMethod;
167
- }
168
- return function(...rawParams) {
169
- const deduplicationKey = getDeduplicationKey(p, rawParams);
170
- if (deduplicationKey === void 0) {
171
- return subscriptionMethod(...rawParams);
172
- }
173
- if (cache.has(deduplicationKey)) {
174
- return cache.get(deduplicationKey);
175
- }
176
- const iterableFactory = getCachedAbortableIterableFactory({
177
- getAbortSignalFromInputArgs: ({ abortSignal }) => abortSignal,
178
- getCacheKeyFromInputArgs: () => deduplicationKey,
179
- async onCacheHit(_iterable, _config) {
180
- },
181
- async onCreateIterable(abortSignal, config) {
182
- const pendingSubscription2 = subscriptionMethod(
183
- ...rawParams
184
- );
185
- const iterable = await pendingSubscription2.subscribe({
186
- ...config,
187
- abortSignal
188
- });
189
- registerIterableCleanup2(iterable, () => {
190
- cache.delete(deduplicationKey);
191
- });
192
- return iterable;
193
- }
194
- });
195
- const pendingSubscription = {
196
- async subscribe(...args) {
197
- const iterable = await iterableFactory(...args);
198
- const { abortSignal } = args[0];
199
- let abortPromise;
200
- return {
201
- ...iterable,
202
- async *[Symbol.asyncIterator]() {
203
- abortPromise ||= abortSignal.aborted ? Promise.reject(EXPLICIT_ABORT_TOKEN ||= createExplicitAbortToken()) : new Promise((_, reject) => {
204
- abortSignal.addEventListener("abort", () => {
205
- reject(EXPLICIT_ABORT_TOKEN ||= createExplicitAbortToken());
206
- });
207
- });
208
- try {
209
- const iterator = iterable[Symbol.asyncIterator]();
210
- while (true) {
211
- const iteratorResult = await Promise.race([iterator.next(), abortPromise]);
212
- if (iteratorResult.done) {
213
- return;
214
- } else {
215
- yield iteratorResult.value;
216
- }
217
- }
218
- } catch (e) {
219
- if (e === (EXPLICIT_ABORT_TOKEN ||= createExplicitAbortToken())) {
220
- return;
221
- }
222
- cache.delete(deduplicationKey);
223
- throw e;
224
- }
225
- }
226
- };
227
- }
137
+ let mostFreeChannel;
138
+ for (let ii = 0; ii < pool.entries.length; ii++) {
139
+ const nextPoolIndex = (pool.freeChannelIndex + ii + 2) % pool.entries.length;
140
+ const nextPoolEntry = (
141
+ // Start from the item two positions after the current item. This way, the
142
+ // search will finish on the item after the current one. This ensures that, if
143
+ // any channels tie for having the most capacity, the one that will be chosen is
144
+ // the one immediately to the current one's right (wrapping around).
145
+ pool.entries[nextPoolIndex]
146
+ );
147
+ if (nextPoolEntry.subscriptionCount < maxSubscriptionsPerChannel && (!mostFreeChannel || mostFreeChannel.subscriptionCount >= nextPoolEntry.subscriptionCount)) {
148
+ mostFreeChannel = {
149
+ poolIndex: nextPoolIndex,
150
+ subscriptionCount: nextPoolEntry.subscriptionCount
228
151
  };
229
- cache.set(deduplicationKey, pendingSubscription);
230
- return pendingSubscription;
231
- };
152
+ }
232
153
  }
233
- });
234
- }
235
-
236
- // src/rpc-subscriptions-autopinger.ts
237
- var PING_PAYLOAD = {
238
- jsonrpc: "2.0",
239
- method: "ping"
240
- };
241
- function getWebSocketTransportWithAutoping({
242
- intervalMs,
243
- transport
244
- }) {
245
- const pingableConnections = /* @__PURE__ */ new Map();
246
- return async (...args) => {
247
- const connection = await transport(...args);
248
- let intervalId;
249
- function sendPing() {
250
- connection.send_DO_NOT_USE_OR_YOU_WILL_BE_FIRED(PING_PAYLOAD);
154
+ pool.freeChannelIndex = mostFreeChannel?.poolIndex ?? -1;
155
+ }
156
+ return function getExistingChannelWithMostCapacityOrCreateChannel({ abortSignal }) {
157
+ let poolEntry;
158
+ function destroyPoolEntry() {
159
+ const index = pool.entries.findIndex((entry) => entry === poolEntry);
160
+ pool.entries.splice(index, 1);
161
+ poolEntry.dispose();
162
+ recomputeFreeChannelIndex();
251
163
  }
252
- function restartPingTimer() {
253
- clearInterval(intervalId);
254
- intervalId = setInterval(sendPing, intervalMs);
164
+ if (pool.freeChannelIndex === -1) {
165
+ const abortController = new AbortController();
166
+ const newChannelPromise = createChannel({ abortSignal: abortController.signal });
167
+ newChannelPromise.then((newChannel) => {
168
+ newChannel.on("error", destroyPoolEntry, { signal: abortController.signal });
169
+ }).catch(destroyPoolEntry);
170
+ poolEntry = {
171
+ channel: newChannelPromise,
172
+ dispose() {
173
+ abortController.abort();
174
+ },
175
+ subscriptionCount: 0
176
+ };
177
+ pool.entries.push(poolEntry);
178
+ } else {
179
+ poolEntry = pool.entries[pool.freeChannelIndex];
255
180
  }
256
- if (pingableConnections.has(connection) === false) {
257
- pingableConnections.set(connection, {
258
- [Symbol.asyncIterator]: connection[Symbol.asyncIterator].bind(connection),
259
- send_DO_NOT_USE_OR_YOU_WILL_BE_FIRED: (...args2) => {
260
- restartPingTimer();
261
- return connection.send_DO_NOT_USE_OR_YOU_WILL_BE_FIRED(...args2);
262
- }
263
- });
264
- (async () => {
265
- try {
266
- for await (const _ of connection) {
267
- restartPingTimer();
268
- }
269
- } catch {
270
- } finally {
271
- pingableConnections.delete(connection);
272
- clearInterval(intervalId);
273
- if (handleOffline) {
274
- globalThis.window.removeEventListener("offline", handleOffline);
275
- }
276
- if (handleOnline) {
277
- globalThis.window.removeEventListener("online", handleOnline);
278
- }
279
- }
280
- })();
281
- if (globalThis.navigator.onLine) {
282
- restartPingTimer();
181
+ poolEntry.subscriptionCount++;
182
+ abortSignal.addEventListener("abort", function destroyConsumer() {
183
+ poolEntry.subscriptionCount--;
184
+ if (poolEntry.subscriptionCount === 0) {
185
+ destroyPoolEntry();
186
+ } else if (pool.freeChannelIndex !== -1) {
187
+ pool.freeChannelIndex--;
188
+ recomputeFreeChannelIndex();
283
189
  }
284
- let handleOffline;
285
- let handleOnline;
286
- {
287
- handleOffline = () => {
288
- clearInterval(intervalId);
289
- };
290
- handleOnline = () => {
291
- sendPing();
292
- restartPingTimer();
293
- };
294
- globalThis.window.addEventListener("offline", handleOffline);
295
- globalThis.window.addEventListener("online", handleOnline);
296
- }
297
- }
298
- return pingableConnections.get(connection);
190
+ });
191
+ recomputeFreeChannelIndex();
192
+ return poolEntry.channel;
299
193
  };
300
194
  }
195
+ function getRpcSubscriptionsChannelWithJSONSerialization(channel) {
196
+ return functional.pipe(
197
+ channel,
198
+ (c) => rpcSubscriptionsSpec.transformChannelInboundMessages(c, JSON.parse),
199
+ (c) => rpcSubscriptionsSpec.transformChannelOutboundMessages(c, JSON.stringify)
200
+ );
201
+ }
202
+ function getRpcSubscriptionsChannelWithBigIntJSONSerialization(channel) {
203
+ return functional.pipe(
204
+ channel,
205
+ (c) => rpcSubscriptionsSpec.transformChannelInboundMessages(c, rpcSpecTypes.parseJsonWithBigInts),
206
+ (c) => rpcSubscriptionsSpec.transformChannelOutboundMessages(c, rpcSpecTypes.stringifyJsonWithBigints)
207
+ );
208
+ }
301
209
 
302
- // src/rpc-subscriptions-connection-sharding.ts
303
- var NULL_SHARD_CACHE_KEY;
304
- function createNullShardCacheKey() {
305
- return Symbol(process.env.NODE_ENV !== "production" ? "Cache key to use when there is no connection sharding strategy" : void 0);
210
+ // src/rpc-subscriptions-channel.ts
211
+ function createDefaultSolanaRpcSubscriptionsChannelCreator(config) {
212
+ return createDefaultRpcSubscriptionsChannelCreatorImpl({
213
+ ...config,
214
+ jsonSerializer: getRpcSubscriptionsChannelWithBigIntJSONSerialization
215
+ });
306
216
  }
307
- function getWebSocketTransportWithConnectionSharding({
308
- getShard,
309
- transport
310
- }) {
311
- return getCachedAbortableIterableFactory({
312
- getAbortSignalFromInputArgs: ({ signal }) => signal,
313
- getCacheKeyFromInputArgs: ({ payload }) => getShard ? getShard(payload) : NULL_SHARD_CACHE_KEY ||= createNullShardCacheKey(),
314
- onCacheHit: (connection, { payload }) => connection.send_DO_NOT_USE_OR_YOU_WILL_BE_FIRED(payload),
315
- onCreateIterable: (abortSignal, config) => transport({
316
- ...config,
217
+ function createDefaultRpcSubscriptionsChannelCreator(config) {
218
+ return createDefaultRpcSubscriptionsChannelCreatorImpl({
219
+ ...config,
220
+ jsonSerializer: getRpcSubscriptionsChannelWithJSONSerialization
221
+ });
222
+ }
223
+ function createDefaultRpcSubscriptionsChannelCreatorImpl(config) {
224
+ if (/^wss?:/i.test(config.url) === false) {
225
+ const protocolMatch = config.url.match(/^([^:]+):/);
226
+ throw new DOMException(
227
+ protocolMatch ? `Failed to construct 'WebSocket': The URL's scheme must be either 'ws' or 'wss'. '${protocolMatch[1]}:' is not allowed.` : `Failed to construct 'WebSocket': The URL '${config.url}' is invalid.`
228
+ );
229
+ }
230
+ const { intervalMs, ...rest } = config;
231
+ const createDefaultRpcSubscriptionsChannel = ({ abortSignal }) => {
232
+ return rpcSubscriptionsChannelWebsocket.createWebSocketChannel({
233
+ ...rest,
234
+ sendBufferHighWatermark: config.sendBufferHighWatermark ?? // Let 128KB of data into the WebSocket buffer before buffering it in the app.
235
+ 131072,
317
236
  signal: abortSignal
318
- })
237
+ }).then(config.jsonSerializer).then(
238
+ (channel) => getRpcSubscriptionsChannelWithAutoping({
239
+ abortSignal,
240
+ channel,
241
+ intervalMs: intervalMs ?? 5e3
242
+ })
243
+ );
244
+ };
245
+ return getChannelPoolingChannelCreator(createDefaultRpcSubscriptionsChannel, {
246
+ maxSubscriptionsPerChannel: config.maxSubscriptionsPerChannel ?? /**
247
+ * A note about this default. The idea here is that, because some RPC providers impose
248
+ * an upper limit on the number of subscriptions you can make per channel, we must
249
+ * choose a number low enough to avoid hitting that limit. Without knowing what provider
250
+ * a given person is using, or what their limit is, we have to choose the lowest of all
251
+ * known limits. As of this writing (October 2024) that is the public mainnet RPC node
252
+ * (api.mainnet-beta.solana.com) at 100 subscriptions.
253
+ */
254
+ 100,
255
+ minChannels: config.minChannels ?? 1
319
256
  });
320
257
  }
258
+ function getRpcSubscriptionsTransportWithSubscriptionCoalescing(transport) {
259
+ const cache = /* @__PURE__ */ new Map();
260
+ return function rpcSubscriptionsTransportWithSubscriptionCoalescing(config) {
261
+ const { request, signal } = config;
262
+ const subscriptionConfigurationHash = fastStableStringify__default.default([request.methodName, request.params]);
263
+ let cachedDataPublisherPromise = cache.get(subscriptionConfigurationHash);
264
+ if (!cachedDataPublisherPromise) {
265
+ const abortController = new AbortController();
266
+ const dataPublisherPromise = transport({
267
+ ...config,
268
+ signal: abortController.signal
269
+ });
270
+ dataPublisherPromise.then((dataPublisher) => {
271
+ dataPublisher.on(
272
+ "error",
273
+ () => {
274
+ cache.delete(subscriptionConfigurationHash);
275
+ abortController.abort();
276
+ },
277
+ { signal: abortController.signal }
278
+ );
279
+ }).catch(() => {
280
+ });
281
+ cache.set(
282
+ subscriptionConfigurationHash,
283
+ cachedDataPublisherPromise = {
284
+ abortController,
285
+ dataPublisherPromise,
286
+ numSubscribers: 0
287
+ }
288
+ );
289
+ }
290
+ cachedDataPublisherPromise.numSubscribers++;
291
+ signal.addEventListener(
292
+ "abort",
293
+ () => {
294
+ cachedDataPublisherPromise.numSubscribers--;
295
+ if (cachedDataPublisherPromise.numSubscribers === 0) {
296
+ queueMicrotask(() => {
297
+ if (cachedDataPublisherPromise.numSubscribers === 0) {
298
+ cache.delete(subscriptionConfigurationHash);
299
+ cachedDataPublisherPromise.abortController.abort();
300
+ }
301
+ });
302
+ }
303
+ },
304
+ { signal: cachedDataPublisherPromise.abortController.signal }
305
+ );
306
+ return cachedDataPublisherPromise.dataPublisherPromise;
307
+ };
308
+ }
321
309
 
322
310
  // src/rpc-subscriptions-transport.ts
323
- function createDefaultRpcSubscriptionsTransport(config) {
324
- const { getShard, intervalMs, ...rest } = config;
311
+ function createDefaultRpcSubscriptionsTransport({
312
+ createChannel
313
+ }) {
325
314
  return functional.pipe(
326
- rpcSubscriptionsTransportWebsocket.createWebSocketTransport({
327
- ...rest,
328
- sendBufferHighWatermark: config.sendBufferHighWatermark ?? // Let 128KB of data into the WebSocket buffer before buffering it in the app.
329
- 131072
330
- }),
331
- (transport) => getWebSocketTransportWithAutoping({
332
- intervalMs: intervalMs ?? 5e3,
333
- transport
334
- }),
335
- (transport) => getWebSocketTransportWithConnectionSharding({
336
- getShard,
337
- transport
338
- })
315
+ createRpcSubscriptionsTransportFromChannelCreator(
316
+ createChannel
317
+ ),
318
+ (transport) => getRpcSubscriptionsTransportWithSubscriptionCoalescing(transport)
339
319
  );
340
320
  }
321
+ function createRpcSubscriptionsTransportFromChannelCreator(createChannel) {
322
+ return async ({ execute, signal }) => {
323
+ const channel = await createChannel({ abortSignal: signal });
324
+ return await execute({ channel, signal });
325
+ };
326
+ }
341
327
 
342
328
  // src/rpc-subscriptions.ts
343
- function createSolanaRpcSubscriptions(clusterUrl, config) {
344
- const transport = createDefaultRpcSubscriptionsTransport({ url: clusterUrl, ...config });
329
+ function createSolanaRpcSubscriptionsImpl(clusterUrl, config) {
330
+ const transport = createDefaultRpcSubscriptionsTransport({
331
+ createChannel: createDefaultSolanaRpcSubscriptionsChannelCreator({ ...config, url: clusterUrl })
332
+ });
345
333
  return createSolanaRpcSubscriptionsFromTransport(transport);
346
334
  }
335
+ function createSolanaRpcSubscriptions(clusterUrl, config) {
336
+ return createSolanaRpcSubscriptionsImpl(clusterUrl, config);
337
+ }
347
338
  function createSolanaRpcSubscriptions_UNSTABLE(clusterUrl, config) {
348
- return createSolanaRpcSubscriptions(
339
+ return createSolanaRpcSubscriptionsImpl(
349
340
  clusterUrl,
350
341
  config
351
342
  );
352
343
  }
353
344
  function createSolanaRpcSubscriptionsFromTransport(transport) {
354
- return functional.pipe(
355
- rpcSubscriptionsSpec.createSubscriptionRpc({
356
- api: rpcSubscriptionsApi.createSolanaRpcSubscriptionsApi(DEFAULT_RPC_SUBSCRIPTIONS_CONFIG),
357
- transport
358
- }),
359
- (rpcSubscriptions) => getRpcSubscriptionsWithSubscriptionCoalescing({
360
- getDeduplicationKey: (...args) => fastStableStringify__default.default(args),
361
- rpcSubscriptions
362
- })
363
- );
364
- }
365
- function createSolanaRpcSubscriptionsFromTransport_UNSTABLE(transport) {
366
- return createSolanaRpcSubscriptionsFromTransport(transport);
345
+ return rpcSubscriptionsSpec.createSubscriptionRpc({
346
+ api: rpcSubscriptionsApi.createSolanaRpcSubscriptionsApi(DEFAULT_RPC_SUBSCRIPTIONS_CONFIG),
347
+ transport
348
+ });
367
349
  }
368
350
 
369
351
  exports.DEFAULT_RPC_SUBSCRIPTIONS_CONFIG = DEFAULT_RPC_SUBSCRIPTIONS_CONFIG;
352
+ exports.createDefaultRpcSubscriptionsChannelCreator = createDefaultRpcSubscriptionsChannelCreator;
370
353
  exports.createDefaultRpcSubscriptionsTransport = createDefaultRpcSubscriptionsTransport;
354
+ exports.createDefaultSolanaRpcSubscriptionsChannelCreator = createDefaultSolanaRpcSubscriptionsChannelCreator;
355
+ exports.createRpcSubscriptionsTransportFromChannelCreator = createRpcSubscriptionsTransportFromChannelCreator;
371
356
  exports.createSolanaRpcSubscriptions = createSolanaRpcSubscriptions;
372
357
  exports.createSolanaRpcSubscriptionsFromTransport = createSolanaRpcSubscriptionsFromTransport;
373
- exports.createSolanaRpcSubscriptionsFromTransport_UNSTABLE = createSolanaRpcSubscriptionsFromTransport_UNSTABLE;
374
358
  exports.createSolanaRpcSubscriptions_UNSTABLE = createSolanaRpcSubscriptions_UNSTABLE;
359
+ exports.getRpcSubscriptionsChannelWithJSONSerialization = getRpcSubscriptionsChannelWithJSONSerialization;
375
360
  Object.keys(rpcSubscriptionsApi).forEach(function (k) {
376
361
  if (k !== 'default' && !Object.prototype.hasOwnProperty.call(exports, k)) Object.defineProperty(exports, k, {
377
362
  enumerable: true,